stella-coder 5.3.1 → 5.3.3

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.
Files changed (52) hide show
  1. package/COMMANDS.md +47 -0
  2. package/install-av.bat +17 -0
  3. package/install-stella-pkg.bat +21 -0
  4. package/oneline-av.ps1 +7 -0
  5. package/oneline-stella.ps1 +2 -0
  6. package/package.json +1 -1
  7. package/releases/stella-antivirus/database.mjs +871 -0
  8. package/releases/stella-antivirus/index.mjs +8 -0
  9. package/releases/stella-antivirus/install-av.bat +12 -0
  10. package/releases/stella-antivirus/scanner.mjs +591 -0
  11. package/releases/stella-antivirus/ui.mjs +570 -0
  12. package/releases/stella-antivirus.zip +0 -0
  13. package/releases/stella-coder/README.md +67 -0
  14. package/releases/stella-coder/adb.mjs +200 -0
  15. package/releases/stella-coder/autonomous-agent.mjs +550 -0
  16. package/releases/stella-coder/banner.mjs +46 -0
  17. package/releases/stella-coder/browser-control.mjs +274 -0
  18. package/releases/stella-coder/build.mjs +151 -0
  19. package/releases/stella-coder/charts.mjs +411 -0
  20. package/releases/stella-coder/coding-brain.mjs +753 -0
  21. package/releases/stella-coder/game-engine.mjs +708 -0
  22. package/releases/stella-coder/gdrive-backup.mjs +338 -0
  23. package/releases/stella-coder/git-api.mjs +407 -0
  24. package/releases/stella-coder/gmail.mjs +415 -0
  25. package/releases/stella-coder/home-assistant.mjs +168 -0
  26. package/releases/stella-coder/index.mjs +5810 -0
  27. package/releases/stella-coder/install-stella.bat +12 -0
  28. package/releases/stella-coder/markdown.mjs +100 -0
  29. package/releases/stella-coder/mcp.mjs +296 -0
  30. package/releases/stella-coder/package.json +67 -0
  31. package/releases/stella-coder/presentations.mjs +1106 -0
  32. package/releases/stella-coder/protect.mjs +182 -0
  33. package/releases/stella-coder/screen-monitor.mjs +334 -0
  34. package/releases/stella-coder/sea-config.json +5 -0
  35. package/releases/stella-coder/security.mjs +237 -0
  36. package/releases/stella-coder/subagents.mjs +142 -0
  37. package/releases/stella-coder/telegram-bot.mjs +824 -0
  38. package/releases/stella-coder/tg-server.mjs +116 -0
  39. package/releases/stella-coder/theme.mjs +91 -0
  40. package/releases/stella-coder/tools.mjs +3143 -0
  41. package/releases/stella-coder/web-parser.mjs +229 -0
  42. package/releases/stella-coder/yandex-maps.mjs +426 -0
  43. package/releases/stella-coder.zip +0 -0
  44. package/run-antivirus.bat +4 -0
  45. package/setup-antivirus.bat +64 -0
  46. package/setup-full.bat +65 -0
  47. package/setup-full.ps1 +84 -0
  48. package/setup-stella.bat +46 -0
  49. package/stella-cli/index.mjs +12 -2
  50. package/stella-cli/protect.mjs +182 -0
  51. package/stella.bat +3 -0
  52. package/tests/test-modules.mjs +101 -0
@@ -0,0 +1,753 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { execSync } from "node:child_process"
4
+ import { generateText } from "ai"
5
+
6
+ // ═══════════════════════════════════════════════════════════════════
7
+ // STELLA CODING BRAIN — Огромный контекст + TDD + Git экосистема
8
+ // ═══════════════════════════════════════════════════════════════════
9
+
10
+ const MAX_CONTEXT_CHARS = 800_000 // ~200K токенов контекст
11
+ const COMPRESS_THRESHOLD = 600_000 // сжимать при превышении
12
+ const MAX_FILE_READ = 50_000 // макс. размер файла для чтения в контекст
13
+ const MAX_FILES_IN_CONTEXT = 50 // макс. файлов в контексте
14
+
15
+ // ═══════════════════════════════════════════════════════════════════
16
+ // 1. REPO MAPPER — карта всего репозитория
17
+ // ═══════════════════════════════════════════════════════════════════
18
+
19
+ export function buildRepoMap(cwd) {
20
+ const ignoreDirs = new Set([
21
+ "node_modules", ".git", "dist", "build", ".next", ".cache",
22
+ "__pycache__", ".tox", "venv", ".venv", "coverage",
23
+ ".nyc_output", "tmp", ".temp", "public/static",
24
+ ])
25
+ const ignoreExts = new Set([
26
+ ".lock", ".sum", ".map", ".min.js", ".min.css",
27
+ ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico",
28
+ ".woff", ".woff2", ".ttf", ".eot",
29
+ ])
30
+
31
+ const tree = []
32
+ const fileTypes = {}
33
+ let totalSize = 0
34
+ let fileCount = 0
35
+
36
+ function scan(dir, prefix = "") {
37
+ try {
38
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
39
+ entries.sort((a, b) => {
40
+ if (a.isDirectory() && !b.isDirectory()) return -1
41
+ if (!a.isDirectory() && b.isDirectory()) return 1
42
+ return a.name.localeCompare(b.name)
43
+ })
44
+
45
+ for (const entry of entries) {
46
+ if (ignoreDirs.has(entry.name)) continue
47
+ if (entry.name.startsWith(".") && entry.name !== ".env.example" && entry.name !== ".gitignore") continue
48
+
49
+ const fullPath = path.join(dir, entry.name)
50
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name
51
+
52
+ if (entry.isDirectory()) {
53
+ const childCount = countFilesSafe(fullPath, ignoreDirs)
54
+ tree.push(`${prefix}📁 ${entry.name}/ (${childCount} files)`)
55
+ scan(fullPath, relPath)
56
+ } else {
57
+ const ext = path.extname(entry.name).toLowerCase()
58
+ fileTypes[ext] = (fileTypes[ext] || 0) + 1
59
+ try {
60
+ const stat = fs.statSync(fullPath)
61
+ totalSize += stat.size
62
+ fileCount++
63
+ tree.push(`${prefix}📄 ${entry.name} (${formatSize(stat.size)})`)
64
+ } catch {}
65
+ }
66
+ }
67
+ } catch {}
68
+ }
69
+
70
+ scan(cwd)
71
+
72
+ return {
73
+ tree: tree.slice(0, 500), // лимит строк
74
+ fileTypes,
75
+ totalSize: formatSize(totalSize),
76
+ fileCount,
77
+ summary: `${fileCount} файлов, ${formatSize(totalSize)}`,
78
+ }
79
+ }
80
+
81
+ function countFilesSafe(dir, ignoreDirs) {
82
+ let count = 0
83
+ try {
84
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
85
+ for (const e of entries) {
86
+ if (ignoreDirs.has(e.name)) continue
87
+ if (e.isFile()) count++
88
+ else if (e.isDirectory()) count += countFilesSafe(path.join(dir, e.name), ignoreDirs)
89
+ }
90
+ } catch {}
91
+ return count
92
+ }
93
+
94
+ function formatSize(bytes) {
95
+ if (bytes < 1024) return `${bytes}B`
96
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`
97
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`
98
+ }
99
+
100
+ // ═══════════════════════════════════════════════════════════════════
101
+ // 2. CONTEXT BUILDER — сборка огромного контекста
102
+ // ═══════════════════════════════════════════════════════════════════
103
+
104
+ export function buildProjectContext(cwd) {
105
+ const context = { parts: [], totalChars: 0 }
106
+
107
+ // 1. Repo map
108
+ const map = buildRepoMap(cwd)
109
+ addPart(context, `# Карта репозитория\n\n${map.summary}\n\n\`\`\`\n${map.tree.join("\n")}\n\`\`\``)
110
+
111
+ // 2. Package files
112
+ for (const pkgFile of ["package.json", "tsconfig.json", "Cargo.toml", "pyproject.toml", "go.mod", "composer.json"]) {
113
+ const p = path.join(cwd, pkgFile)
114
+ if (fs.existsSync(p)) {
115
+ const content = readFileSafe(p, 5000)
116
+ if (content) addPart(context, `# ${pkgFile}\n\n\`\`\`json\n${content}\n\`\`\``)
117
+ }
118
+ }
119
+
120
+ // 3. Config files
121
+ for (const cfgFile of [".env.example", ".gitignore", "eslint.config.mjs", "prettier.config.js", "biome.json"]) {
122
+ const p = path.join(cwd, cfgFile)
123
+ if (fs.existsSync(p)) {
124
+ const content = readFileSafe(p, 3000)
125
+ if (content) addPart(context, `# ${cfgFile}\n\n${content}`)
126
+ }
127
+ }
128
+
129
+ // 4. SPEC.md / CLAUDE.md / STELLA.md
130
+ for (const specFile of ["SPEC.md", "CLAUDE.md", "STELLA.md", "README.md", "AGENTS.md"]) {
131
+ const p = path.join(cwd, specFile)
132
+ if (fs.existsSync(p)) {
133
+ const content = readFileSafe(p, 20000)
134
+ if (content) addPart(context, `# ${specFile}\n\n${content}`)
135
+ }
136
+ }
137
+
138
+ // 5. Key source files (auto-detect important ones)
139
+ const importantFiles = detectImportantFiles(cwd)
140
+ for (const f of importantFiles) {
141
+ const content = readFileSafe(f.fullPath, MAX_FILE_READ)
142
+ if (content) {
143
+ addPart(context, `# ${f.relPath}\n\n\`\`\`${f.lang}\n${content}\n\`\`\``)
144
+ }
145
+ }
146
+
147
+ return context
148
+ }
149
+
150
+ function addPart(ctx, text) {
151
+ if (ctx.totalChars + text.length > MAX_CONTEXT_CHARS) return false
152
+ ctx.parts.push(text)
153
+ ctx.totalChars += text.length
154
+ return true
155
+ }
156
+
157
+ function readFileSafe(filePath, maxChars) {
158
+ try {
159
+ const content = fs.readFileSync(filePath, "utf8")
160
+ return content.length > maxChars ? content.slice(0, maxChars) + "\n... [truncated]" : content
161
+ } catch { return null }
162
+ }
163
+
164
+ function detectImportantFiles(cwd) {
165
+ const candidates = []
166
+ const extensions = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".py", ".rs", ".go", ".java", ".cs", ".rb"])
167
+
168
+ function scan(dir, rel = "") {
169
+ try {
170
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
171
+ for (const e of entries) {
172
+ if (["node_modules", ".git", "dist", "build", ".next"].includes(e.name)) continue
173
+ const fullPath = path.join(dir, e.name)
174
+ const relPath = rel ? `${rel}/${e.name}` : e.name
175
+
176
+ if (e.isDirectory()) {
177
+ scan(fullPath, relPath)
178
+ } else {
179
+ const ext = path.extname(e.name).toLowerCase()
180
+ if (!extensions.has(ext)) continue
181
+
182
+ try {
183
+ const stat = fs.statSync(fullPath)
184
+ const isIndex = /index\.(ts|tsx|js|jsx|mjs)$/.test(e.name)
185
+ const isMain = /^(main|app|server|cli|index)\./.test(e.name)
186
+ const isConfig = /config|settings|constants/.test(e.name.toLowerCase())
187
+ const depth = relPath.split("/").length
188
+
189
+ let priority = 0
190
+ if (isMain) priority += 100
191
+ if (isIndex) priority += 80
192
+ if (isConfig) priority += 60
193
+ if (depth <= 2) priority += 40
194
+ if (stat.size > 1000 && stat.size < 50000) priority += 20
195
+
196
+ candidates.push({
197
+ fullPath,
198
+ relPath,
199
+ lang: ext.slice(1),
200
+ size: stat.size,
201
+ priority,
202
+ })
203
+ } catch {}
204
+ }
205
+ }
206
+ } catch {}
207
+ }
208
+
209
+ scan(cwd)
210
+
211
+ return candidates
212
+ .sort((a, b) => b.priority - a.priority)
213
+ .slice(0, MAX_FILES_IN_CONTEXT)
214
+ }
215
+
216
+ // ═══════════════════════════════════════════════════════════════════
217
+ // 3. CONTEXT COMPRESSOR — сжатие контекста
218
+ // ═══════════════════════════════════════════════════════════════════
219
+
220
+ export async function compressContext(messages, model) {
221
+ if (JSON.stringify(messages).length < COMPRESS_THRESHOLD) return messages
222
+
223
+ const summaryPrompt = `Ты — эксперт по сжатию контекста. Сожми этот диалог в краткое резюме.
224
+
225
+ Включи:
226
+ 1. Цели и задачи пользователя
227
+ 2. Сделанные изменения (какие файлы, что изменено)
228
+ 3. Текущий статус (что работает, что нет)
229
+ 4. Важные решения и их обоснование
230
+ 5. Следующие шаги
231
+
232
+ Пиши кратко, по-русски, используя markdown.
233
+ Максимум 3000 символов.
234
+
235
+ Диалог:
236
+ ${messages.map(m => `${m.role}: ${typeof m.content === "string" ? m.content : JSON.stringify(m.content).slice(0, 2000)}`).join("\n\n")}`
237
+
238
+ try {
239
+ const { text } = await generateText({ model, messages: [{ role: "user", content: summaryPrompt }] })
240
+ return [
241
+ { role: "user", content: `Резюме предыдущего диалога (сжато):\n\n${text}` },
242
+ { role: "assistant", content: "Понял. Контекст сжат, продолжаем работу с этим резюме." },
243
+ ]
244
+ } catch {
245
+ // если сжатие не удалось — просто обрезаем старые сообщения
246
+ return messages.slice(-20)
247
+ }
248
+ }
249
+
250
+ // ═══════════════════════════════════════════════════════════════════
251
+ // 4. SPEC ENGINE — автономная работа по SPEC.md / CLAUDE.md
252
+ // ═══════════════════════════════════════════════════════════════════
253
+
254
+ export function loadSpec(cwd) {
255
+ for (const specFile of ["SPEC.md", "CLAUDE.md", "AGENTS.md", "STELLA.md"]) {
256
+ const p = path.join(cwd, specFile)
257
+ if (fs.existsSync(p)) {
258
+ const content = fs.readFileSync(p, "utf8")
259
+ return { file: specFile, content, exists: true }
260
+ }
261
+ }
262
+ return { file: null, content: null, exists: false }
263
+ }
264
+
265
+ export function generateSpecTemplate(projectName, description) {
266
+ return `# SPEC: ${projectName}
267
+
268
+ ## Описание
269
+ ${description || "Опишите назначение проекта"}
270
+
271
+ ## Архитектура
272
+ - Опишите ключевые компоненты
273
+ - Укажите зависимости между модулями
274
+
275
+ ## API / Интерфейсы
276
+ - Опишите публичные интерфейсы
277
+
278
+ ## Требования
279
+ ### Функциональные
280
+ - [ ] Требование 1
281
+ - [ ] Требование 2
282
+
283
+ ### Нефункциональные
284
+ - [ ] Производительность
285
+ - [ ] Безопасность
286
+ - [ ] Тестируемость
287
+
288
+ ## Тесты
289
+ - Unit-тесты для каждого модуля
290
+ - Интеграционные тесты для API
291
+ - E2E тесты для критических путей
292
+
293
+ ## Структура файлов
294
+ \`\`\`
295
+ src/
296
+ ├── index.ts # Точка входа
297
+ ├── modules/ # Модули бизнес-логики
298
+ ├── utils/ # Утилиты
299
+ └── types/ # Типы
300
+ \`\`\`
301
+ `
302
+ }
303
+
304
+ // ═══════════════════════════════════════════════════════════════════
305
+ // 5. TDD ENGINE — автономный тест-драйв
306
+ // ═══════════════════════════════════════════════════════════════════
307
+
308
+ export function detectTestFramework(cwd) {
309
+ // Check for common test frameworks
310
+ const checks = [
311
+ { file: "package.json", pattern: /"vitest"|"jest"|"mocha"|"ava"|"tap"/, name: "package.json" },
312
+ { file: "pyproject.toml", pattern: /pytest|unittest/, name: "pyproject.toml" },
313
+ { file: "Cargo.toml", pattern: /\[dev-dependencies\]/, name: "Cargo.toml" },
314
+ { file: "go.mod", pattern: /testify|testing/, name: "go.mod" },
315
+ ]
316
+
317
+ for (const check of checks) {
318
+ const p = path.join(cwd, check.file)
319
+ if (fs.existsSync(p)) {
320
+ const content = fs.readFileSync(p, "utf8")
321
+ const match = content.match(check.pattern)
322
+ if (match) {
323
+ return {
324
+ detected: true,
325
+ framework: match[0].replace(/["'\[\]]/g, ""),
326
+ config: check.file,
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ // Check for test directories
333
+ const testDirs = ["__tests__", "tests", "test", "spec", "src/__tests__"]
334
+ for (const d of testDirs) {
335
+ if (fs.existsSync(path.join(cwd, d))) {
336
+ return { detected: true, framework: "unknown", config: d }
337
+ }
338
+ }
339
+
340
+ return { detected: false, framework: null, config: null }
341
+ }
342
+
343
+ export function detectLinter(cwd) {
344
+ const linters = [
345
+ { config: ".eslintrc.js", name: "eslint", cmd: "npx eslint" },
346
+ { config: ".eslintrc.json", name: "eslint", cmd: "npx eslint" },
347
+ { config: ".eslintrc.yml", name: "eslint", cmd: "npx eslint" },
348
+ { config: "eslint.config.mjs", name: "eslint", cmd: "npx eslint" },
349
+ { config: "eslint.config.js", name: "eslint", cmd: "npx eslint" },
350
+ { config: "biome.json", name: "biome", cmd: "npx biome check" },
351
+ { config: ".prettierrc", name: "prettier", cmd: "npx prettier --check" },
352
+ { config: "prettier.config.js", name: "prettier", cmd: "npx prettier --check" },
353
+ { config: "pyproject.toml", name: "ruff", cmd: "ruff check" },
354
+ { config: "ruff.toml", name: "ruff", cmd: "ruff check" },
355
+ { config: ".pylintrc", name: "pylint", cmd: "pylint" },
356
+ { config: "clippy.toml", name: "clippy", cmd: "cargo clippy" },
357
+ ]
358
+
359
+ for (const l of linters) {
360
+ if (fs.existsSync(path.join(cwd, l.config))) {
361
+ return { detected: true, ...l }
362
+ }
363
+ }
364
+
365
+ return { detected: false }
366
+ }
367
+
368
+ export function detectFormatter(cwd) {
369
+ const formatters = [
370
+ { config: ".prettierrc", name: "prettier", cmd: "npx prettier --write" },
371
+ { config: "prettier.config.js", name: "prettier", cmd: "npx prettier --write" },
372
+ { config: "biome.json", name: "biome", cmd: "npx biome format --write" },
373
+ { config: ".stylua.toml", name: "stylua", cmd: "stylua" },
374
+ ]
375
+
376
+ for (const f of formatters) {
377
+ if (fs.existsSync(path.join(cwd, f.config))) {
378
+ return { detected: true, ...f }
379
+ }
380
+ }
381
+
382
+ return { detected: false }
383
+ }
384
+
385
+ export function detectTypeChecker(cwd) {
386
+ const checkers = [
387
+ { config: "tsconfig.json", name: "typescript", cmd: "npx tsc --noEmit" },
388
+ { config: "mypy.ini", name: "mypy", cmd: "mypy" },
389
+ { config: ".mypy.ini", name: "mypy", cmd: "mypy" },
390
+ { config: "pyrightconfig.json", name: "pyright", cmd: "npx pyright" },
391
+ ]
392
+
393
+ for (const c of checkers) {
394
+ if (fs.existsSync(path.join(cwd, c.config))) {
395
+ return { detected: true, ...c }
396
+ }
397
+ }
398
+
399
+ return { detected: false }
400
+ }
401
+
402
+ export function generateTestPrompt(filePath, fileContent, spec) {
403
+ const fileName = path.basename(filePath)
404
+ const ext = path.extname(filePath).slice(1)
405
+
406
+ const langMap = {
407
+ ts: "TypeScript", tsx: "TypeScript React", js: "JavaScript",
408
+ jsx: "JavaScript React", py: "Python", rs: "Rust", go: "Go",
409
+ java: "Java", rb: "Ruby", cs: "C#",
410
+ }
411
+
412
+ const lang = langMap[ext] || ext
413
+ const testExt = { ts: "test.ts", tsx: "test.tsx", js: "test.js", jsx: "test.jsx", py: "test.py" }
414
+ const testFile = fileName.replace(new RegExp(`\\.${ext}$`), "") + (testExt[ext] || `.test.${ext}`)
415
+
416
+ return {
417
+ testFile,
418
+ prompt: `Ты — эксперт по TDD. Напиши unit-тесты для файла ${fileName}.
419
+
420
+ Файл: ${filePath}
421
+ Язык: ${lang}
422
+
423
+ Содержимое:
424
+ \`\`\`${ext}
425
+ ${fileContent}
426
+ \`\`\`
427
+
428
+ ${spec ? `SPEC проекта:\n${spec.slice(0, 3000)}` : ""}
429
+
430
+ Сгенерируй ТОЛЬКО код тестов. Правила:
431
+ 1. Используй ${lang} и стандартные библиотеки тестирования
432
+ 2. Покрой все публичные функции/методы
433
+ 3. Включи edge cases и ошибки
434
+ 4. Используй descriptive имена тестов
435
+ 5. Формат: ${testFile}
436
+ 6. Верни ТОЛЬКО код, без объяснений`,
437
+ }
438
+ }
439
+
440
+ // ═══════════════════════════════════════════════════════════════════
441
+ // 6. GIT ECOSYSTEM — профессиональная работа с Git
442
+ // ═══════════════════════════════════════════════════════════════════
443
+
444
+ export function gitStatus(cwd) {
445
+ try {
446
+ const status = execSync("git status --porcelain", { cwd, encoding: "utf8", timeout: 10000 })
447
+ const branch = execSync("git branch --show-current", { cwd, encoding: "utf8", timeout: 5000 }).trim()
448
+ const ahead = execSync("git rev-list --count @{upstream}..HEAD 2>nul || echo 0", { cwd, encoding: "utf8", timeout: 5000 }).trim()
449
+ const behind = execSync("git rev-list --count HEAD..@{upstream} 2>nul || echo 0", { cwd, encoding: "utf8", timeout: 5000 }).trim()
450
+ const stashCount = execSync("git stash list 2>nul | find /c /v \"\" || echo 0", { cwd, encoding: "utf8", timeout: 5000 }).trim()
451
+
452
+ const files = status.split("\n").filter(l => l.trim()).map(l => ({
453
+ status: l.slice(0, 2).trim(),
454
+ path: l.slice(3),
455
+ }))
456
+
457
+ return {
458
+ branch,
459
+ ahead: parseInt(ahead) || 0,
460
+ behind: parseInt(behind) || 0,
461
+ stashCount: parseInt(stashCount) || 0,
462
+ files,
463
+ clean: files.length === 0,
464
+ }
465
+ } catch (e) {
466
+ return { branch: "unknown", ahead: 0, behind: 0, stashCount: 0, files: [], clean: true, error: e.message }
467
+ }
468
+ }
469
+
470
+ export function gitDiff(cwd, file = null) {
471
+ try {
472
+ const cmd = file ? `git diff "${file}"` : "git diff --stat"
473
+ return execSync(cmd, { cwd, encoding: "utf8", timeout: 30000 }).trim()
474
+ } catch { return "" }
475
+ }
476
+
477
+ export function gitLog(cwd, count = 10) {
478
+ try {
479
+ return execSync(`git log --oneline -${count}`, { cwd, encoding: "utf8", timeout: 10000 }).trim()
480
+ } catch { return "" }
481
+ }
482
+
483
+ export function gitBranches(cwd) {
484
+ try {
485
+ const output = execSync("git branch -a --format=%(refname:short)", { cwd, encoding: "utf8", timeout: 10000 })
486
+ return output.trim().split("\n").filter(b => b.trim())
487
+ } catch { return [] }
488
+ }
489
+
490
+ export function gitCreateBranch(cwd, name, startPoint = null) {
491
+ try {
492
+ const cmd = startPoint ? `git checkout -b "${name}" "${startPoint}"` : `git checkout -b "${name}"`
493
+ execSync(cmd, { cwd, encoding: "utf8", timeout: 10000 })
494
+ return { success: true, branch: name }
495
+ } catch (e) {
496
+ return { success: false, error: e.message }
497
+ }
498
+ }
499
+
500
+ export function gitCheckout(cwd, branch) {
501
+ try {
502
+ execSync(`git checkout "${branch}"`, { cwd, encoding: "utf8", timeout: 10000 })
503
+ return { success: true }
504
+ } catch (e) {
505
+ return { success: false, error: e.message }
506
+ }
507
+ }
508
+
509
+ export function gitMerge(cwd, branch) {
510
+ try {
511
+ execSync(`git merge "${branch}" --no-edit`, { cwd, encoding: "utf8", timeout: 30000 })
512
+ return { success: true }
513
+ } catch (e) {
514
+ // Check for merge conflicts
515
+ try {
516
+ const conflicts = execSync("git diff --name-only --diff-filter=U", { cwd, encoding: "utf8", timeout: 5000 })
517
+ if (conflicts.trim()) {
518
+ return {
519
+ success: false,
520
+ conflict: true,
521
+ files: conflicts.trim().split("\n"),
522
+ error: e.message,
523
+ }
524
+ }
525
+ } catch {}
526
+ return { success: false, error: e.message }
527
+ }
528
+ }
529
+
530
+ export function gitStash(cwd, message = null) {
531
+ try {
532
+ const cmd = message ? `git stash push -m "${message}"` : "git stash push"
533
+ execSync(cmd, { cwd, encoding: "utf8", timeout: 10000 })
534
+ return { success: true }
535
+ } catch (e) {
536
+ return { success: false, error: e.message }
537
+ }
538
+ }
539
+
540
+ export function gitStashPop(cwd) {
541
+ try {
542
+ execSync("git stash pop", { cwd, encoding: "utf8", timeout: 10000 })
543
+ return { success: true }
544
+ } catch (e) {
545
+ return { success: false, error: e.message }
546
+ }
547
+ }
548
+
549
+ export function gitCommit(cwd, message) {
550
+ try {
551
+ execSync(`git commit -m "${message}"`, { cwd, encoding: "utf8", timeout: 30000 })
552
+ return { success: true }
553
+ } catch (e) {
554
+ return { success: false, error: e.message }
555
+ }
556
+ }
557
+
558
+ export function gitPush(cwd, branch = null, force = false) {
559
+ try {
560
+ const cmd = force ? "git push --force-with-lease" : `git push${branch ? ` origin ${branch}` : ""}`
561
+ execSync(cmd, { cwd, encoding: "utf8", timeout: 60000 })
562
+ return { success: true }
563
+ } catch (e) {
564
+ return { success: false, error: e.message }
565
+ }
566
+ }
567
+
568
+ export function gitPull(cwd, rebase = false) {
569
+ try {
570
+ const cmd = rebase ? "git pull --rebase" : "git pull"
571
+ execSync(cmd, { cwd, encoding: "utf8", timeout: 60000 })
572
+ return { success: true }
573
+ } catch (e) {
574
+ return { success: false, error: e.message }
575
+ }
576
+ }
577
+
578
+ export function gitCreatePR(cwd, title, body, base = "main") {
579
+ try {
580
+ const cmd = `gh pr create --title "${title}" --body "${body}" --base ${base}`
581
+ const output = execSync(cmd, { cwd, encoding: "utf8", timeout: 30000 })
582
+ return { success: true, url: output.trim().match(/https:\/\/[^\s]+/)?.[0] || "" }
583
+ } catch (e) {
584
+ return { success: false, error: e.message }
585
+ }
586
+ }
587
+
588
+ export function gitListPRs(cwd) {
589
+ try {
590
+ const output = execSync("gh pr list --json number,title,author,state,url", { cwd, encoding: "utf8", timeout: 30000 })
591
+ return JSON.parse(output)
592
+ } catch { return [] }
593
+ }
594
+
595
+ export function gitResolveConflicts(cwd, strategy = "theirs") {
596
+ try {
597
+ const files = execSync("git diff --name-only --diff-filter=U", { cwd, encoding: "utf8", timeout: 5000 })
598
+ if (!files.trim()) return { success: true, message: "No conflicts" }
599
+
600
+ for (const file of files.trim().split("\n")) {
601
+ execSync(`git checkout --${strategy} "${file}"`, { cwd, encoding: "utf8", timeout: 10000 })
602
+ }
603
+ execSync("git add .", { cwd, encoding: "utf8", timeout: 10000 })
604
+ return { success: true, resolved: files.trim().split("\n") }
605
+ } catch (e) {
606
+ return { success: false, error: e.message }
607
+ }
608
+ }
609
+
610
+ // ═══════════════════════════════════════════════════════════════════
611
+ // 7. AUTO-FIX ENGINE — автоматическое исправление
612
+ // ═══════════════════════════════════════════════════════════════════
613
+
614
+ export function runLinter(cwd, linter) {
615
+ if (!linter.detected) return { success: false, error: "Linter not detected" }
616
+
617
+ try {
618
+ const output = execSync(`${linter.cmd} . 2>&1`, { cwd, encoding: "utf8", timeout: 60000 })
619
+ return { success: true, output, hasErrors: false }
620
+ } catch (e) {
621
+ return { success: false, output: e.stdout || e.stderr || e.message, hasErrors: true }
622
+ }
623
+ }
624
+
625
+ export function runFormatter(cwd, formatter) {
626
+ if (!formatter.detected) return { success: false, error: "Formatter not detected" }
627
+
628
+ try {
629
+ execSync(`${formatter.cmd} .`, { cwd, encoding: "utf8", timeout: 60000 })
630
+ return { success: true }
631
+ } catch (e) {
632
+ return { success: false, error: e.message }
633
+ }
634
+ }
635
+
636
+ export function runTypeChecker(cwd, checker) {
637
+ if (!checker.detected) return { success: false, error: "Type checker not detected" }
638
+
639
+ try {
640
+ const output = execSync(checker.cmd, { cwd, encoding: "utf8", timeout: 120000 })
641
+ return { success: true, output, hasErrors: false }
642
+ } catch (e) {
643
+ return { success: false, output: e.stdout || e.stderr || e.message, hasErrors: true }
644
+ }
645
+ }
646
+
647
+ export function runTests(cwd, framework) {
648
+ if (!framework.detected) return { success: false, error: "Test framework not detected" }
649
+
650
+ const testCmds = {
651
+ jest: "npx jest --passWithNoTests",
652
+ vitest: "npx vitest run",
653
+ mocha: "npx mocha",
654
+ pytest: "pytest",
655
+ cargo: "cargo test",
656
+ go: "go test ./...",
657
+ }
658
+
659
+ const cmd = testCmds[framework.framework] || "npm test"
660
+
661
+ try {
662
+ const output = execSync(cmd, { cwd, encoding: "utf8", timeout: 120000 })
663
+ return { success: true, output, passed: true }
664
+ } catch (e) {
665
+ return { success: false, output: e.stdout || e.stderr || e.message, passed: false }
666
+ }
667
+ }
668
+
669
+ // ═══════════════════════════════════════════════════════════════════
670
+ // 8. MULTI-FILE EDITOR — редактирование в 15+ файлах
671
+ // ═══════════════════════════════════════════════════════════════════
672
+
673
+ export function applyEdits(edits, cwd) {
674
+ const results = []
675
+
676
+ for (const edit of edits) {
677
+ const fullPath = path.resolve(cwd, edit.file)
678
+ try {
679
+ if (edit.action === "create") {
680
+ const dir = path.dirname(fullPath)
681
+ fs.mkdirSync(dir, { recursive: true })
682
+ fs.writeFileSync(fullPath, edit.content)
683
+ results.push({ file: edit.file, success: true, action: "created" })
684
+ } else if (edit.action === "edit") {
685
+ let content = fs.readFileSync(fullPath, "utf8")
686
+ if (edit.oldString && edit.newString) {
687
+ content = content.replace(edit.oldString, edit.newString)
688
+ } else if (edit.content) {
689
+ content = edit.content
690
+ }
691
+ fs.writeFileSync(fullPath, content)
692
+ results.push({ file: edit.file, success: true, action: "edited" })
693
+ } else if (edit.action === "delete") {
694
+ fs.unlinkSync(fullPath)
695
+ results.push({ file: edit.file, success: true, action: "deleted" })
696
+ }
697
+ } catch (e) {
698
+ results.push({ file: edit.file, success: false, error: e.message })
699
+ }
700
+ }
701
+
702
+ return results
703
+ }
704
+
705
+ // ═══════════════════════════════════════════════════════════════════
706
+ // 9. COMMANDS — команды для CLI
707
+ // ═══════════════════════════════════════════════════════════════════
708
+
709
+ export const CODING_BRAIN_COMMANDS = {
710
+ "/brain": "показать состояние контекста и инструментов",
711
+ "/brain-map": "построить карту репозитория",
712
+ "/brain-compress": "сжать контекст сессии",
713
+ "/spec": "показать/создать SPEC.md",
714
+ "/tdd": "автономный TDD-цикл (тесты → код → линтер → тесты)",
715
+ "/lint-auto": "автоматический линтер + исправление",
716
+ "/format-auto": "автоформатирование кода",
717
+ "/typecheck-auto": "автопроверка типов",
718
+ "/test-auto": "автозапуск тестов",
719
+ "/git-eco": "полная информация о Git",
720
+ "/git-pr": "создать Pull Request",
721
+ "/git-merge-auto": "автоматический merge с разрешением конфликтов",
722
+ "/fix-all": "полный цикл: линтер → форматер → типы → тесты",
723
+ }
724
+
725
+ // ═══════════════════════════════════════════════════════════════════
726
+ // 10. DIAGNOSTICS — полная диагностика проекта
727
+ // ═══════════════════════════════════════════════════════════════════
728
+
729
+ export function diagnoseProject(cwd) {
730
+ const test = detectTestFramework(cwd)
731
+ const lint = detectLinter(cwd)
732
+ const fmt = detectFormatter(cwd)
733
+ const types = detectTypeChecker(cwd)
734
+ const git = gitStatus(cwd)
735
+ const spec = loadSpec(cwd)
736
+ const map = buildRepoMap(cwd)
737
+
738
+ return {
739
+ repo: map.summary,
740
+ spec: spec.exists ? spec.file : "not found",
741
+ test: test.detected ? test.framework : "not detected",
742
+ linter: lint.detected ? lint.name : "not detected",
743
+ formatter: fmt.detected ? fmt.name : "not detected",
744
+ typeChecker: types.detected ? types.name : "not detected",
745
+ git: {
746
+ branch: git.branch,
747
+ clean: git.clean,
748
+ files: git.files.length,
749
+ ahead: git.ahead,
750
+ behind: git.behind,
751
+ },
752
+ }
753
+ }