stella-coder 3.9.2 → 4.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.
- package/.mcp.json +11 -0
- package/.opencode/skills/banner-design/SKILL.md +196 -0
- package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
- package/.opencode/skills/brand/SKILL.md +97 -0
- package/.opencode/skills/brand/references/approval-checklist.md +169 -0
- package/.opencode/skills/brand/references/asset-organization.md +157 -0
- package/.opencode/skills/brand/references/brand-guideline-template.md +140 -0
- package/.opencode/skills/brand/references/color-palette-management.md +186 -0
- package/.opencode/skills/brand/references/consistency-checklist.md +94 -0
- package/.opencode/skills/brand/references/logo-usage-rules.md +185 -0
- package/.opencode/skills/brand/references/messaging-framework.md +85 -0
- package/.opencode/skills/brand/references/typography-specifications.md +214 -0
- package/.opencode/skills/brand/references/update.md +118 -0
- package/.opencode/skills/brand/references/visual-identity.md +96 -0
- package/.opencode/skills/brand/references/voice-framework.md +88 -0
- package/.opencode/skills/brand/scripts/extract-colors.cjs +341 -0
- package/.opencode/skills/brand/scripts/inject-brand-context.cjs +349 -0
- package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +248 -0
- package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +52 -0
- package/.opencode/skills/brand/scripts/validate-asset.cjs +387 -0
- package/.opencode/skills/brand/templates/brand-guidelines-starter.md +275 -0
- package/.opencode/skills/design/SKILL.md +313 -0
- package/.opencode/skills/design/data/cip/deliverables.csv +51 -0
- package/.opencode/skills/design/data/cip/industries.csv +21 -0
- package/.opencode/skills/design/data/cip/mockup-contexts.csv +21 -0
- package/.opencode/skills/design/data/cip/styles.csv +21 -0
- package/.opencode/skills/design/data/icon/styles.csv +16 -0
- package/.opencode/skills/design/data/logo/colors.csv +56 -0
- package/.opencode/skills/design/data/logo/industries.csv +56 -0
- package/.opencode/skills/design/data/logo/styles.csv +56 -0
- package/.opencode/skills/design/references/banner-sizes-and-styles.md +118 -0
- package/.opencode/skills/design/references/cip-deliverable-guide.md +95 -0
- package/.opencode/skills/design/references/cip-design.md +121 -0
- package/.opencode/skills/design/references/cip-prompt-engineering.md +84 -0
- package/.opencode/skills/design/references/cip-style-guide.md +68 -0
- package/.opencode/skills/design/references/design-routing.md +207 -0
- package/.opencode/skills/design/references/icon-design.md +122 -0
- package/.opencode/skills/design/references/logo-color-psychology.md +101 -0
- package/.opencode/skills/design/references/logo-design.md +92 -0
- package/.opencode/skills/design/references/logo-prompt-engineering.md +158 -0
- package/.opencode/skills/design/references/logo-style-guide.md +109 -0
- package/.opencode/skills/design/references/slides-copywriting-formulas.md +84 -0
- package/.opencode/skills/design/references/slides-create.md +4 -0
- package/.opencode/skills/design/references/slides-html-template.md +295 -0
- package/.opencode/skills/design/references/slides-layout-patterns.md +137 -0
- package/.opencode/skills/design/references/slides-strategies.md +94 -0
- package/.opencode/skills/design/references/slides.md +42 -0
- package/.opencode/skills/design/references/social-photos-design.md +329 -0
- package/.opencode/skills/design/scripts/cip/core.py +215 -0
- package/.opencode/skills/design/scripts/cip/generate.py +484 -0
- package/.opencode/skills/design/scripts/cip/render-html.py +424 -0
- package/.opencode/skills/design/scripts/cip/search.py +127 -0
- package/.opencode/skills/design/scripts/icon/generate.py +487 -0
- package/.opencode/skills/design/scripts/logo/core.py +175 -0
- package/.opencode/skills/design/scripts/logo/generate.py +362 -0
- package/.opencode/skills/design/scripts/logo/search.py +114 -0
- package/.opencode/skills/design-system/SKILL.md +244 -0
- package/.opencode/skills/design-system/data/slide-backgrounds.csv +11 -0
- package/.opencode/skills/design-system/data/slide-charts.csv +26 -0
- package/.opencode/skills/design-system/data/slide-color-logic.csv +14 -0
- package/.opencode/skills/design-system/data/slide-copy.csv +26 -0
- package/.opencode/skills/design-system/data/slide-layout-logic.csv +16 -0
- package/.opencode/skills/design-system/data/slide-layouts.csv +26 -0
- package/.opencode/skills/design-system/data/slide-strategies.csv +16 -0
- package/.opencode/skills/design-system/data/slide-typography.csv +15 -0
- package/.opencode/skills/design-system/references/component-specs.md +236 -0
- package/.opencode/skills/design-system/references/component-tokens.md +214 -0
- package/.opencode/skills/design-system/references/primitive-tokens.md +203 -0
- package/.opencode/skills/design-system/references/semantic-tokens.md +215 -0
- package/.opencode/skills/design-system/references/states-and-variants.md +241 -0
- package/.opencode/skills/design-system/references/tailwind-integration.md +251 -0
- package/.opencode/skills/design-system/references/token-architecture.md +224 -0
- package/.opencode/skills/design-system/scripts/embed-tokens.cjs +99 -0
- package/.opencode/skills/design-system/scripts/fetch-background.py +317 -0
- package/.opencode/skills/design-system/scripts/generate-slide.py +770 -0
- package/.opencode/skills/design-system/scripts/generate-tokens.cjs +205 -0
- package/.opencode/skills/design-system/scripts/html-token-validator.py +327 -0
- package/.opencode/skills/design-system/scripts/search-slides.py +218 -0
- package/.opencode/skills/design-system/scripts/slide-token-validator.py +35 -0
- package/.opencode/skills/design-system/scripts/slide_search_core.py +453 -0
- package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +48 -0
- package/.opencode/skills/design-system/scripts/validate-tokens.cjs +246 -0
- package/.opencode/skills/design-system/templates/design-tokens-starter.json +143 -0
- package/.opencode/skills/slides/SKILL.md +40 -0
- package/.opencode/skills/slides/references/copywriting-formulas.md +84 -0
- package/.opencode/skills/slides/references/create.md +4 -0
- package/.opencode/skills/slides/references/html-template.md +295 -0
- package/.opencode/skills/slides/references/layout-patterns.md +137 -0
- package/.opencode/skills/slides/references/slide-strategies.md +94 -0
- package/.opencode/skills/ui-styling/LICENSE.txt +202 -0
- package/.opencode/skills/ui-styling/SKILL.md +324 -0
- package/.opencode/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/.opencode/skills/ui-styling/references/shadcn-components.md +424 -0
- package/.opencode/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/.opencode/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/.opencode/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/.opencode/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/.opencode/skills/ui-styling/scripts/requirements.txt +17 -0
- package/.opencode/skills/ui-styling/scripts/shadcn_add.py +308 -0
- package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
- package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
- package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +3 -0
- package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
- package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +394 -0
- package/.opencode/skills/ui-ux-pro-max/SKILL.md +396 -0
- package/.opencode/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
- package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
- package/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.opencode/skills/ui-ux-pro-max/data/colors.csv +193 -0
- package/.opencode/skills/ui-ux-pro-max/data/design.csv +1776 -0
- package/.opencode/skills/ui-ux-pro-max/data/draft.csv +1779 -0
- package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
- package/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/.opencode/skills/ui-ux-pro-max/data/motion.csv +17 -0
- package/.opencode/skills/ui-ux-pro-max/data/products.csv +193 -0
- package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +71 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
- package/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
- package/.opencode/skills/ui-ux-pro-max/data/typography.csv +75 -0
- package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
- package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/core.py +274 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1329 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/search.py +127 -0
- package/README.md +5 -0
- package/STELLA.md +1 -1
- package/dist/antimalware/database.mjs +871 -0
- package/dist/antimalware/index.mjs +8 -0
- package/dist/antimalware/scanner.mjs +591 -0
- package/dist/antimalware/ui.mjs +570 -0
- package/dist/banner.mjs +46 -0
- package/dist/index.mjs +3073 -0
- package/dist/markdown.mjs +100 -0
- package/dist/sea-config.json +4 -0
- package/dist/security.mjs +237 -0
- package/dist/theme.mjs +89 -0
- package/dist/tools.mjs +3145 -0
- package/install-stella.bat +44 -0
- package/package.json +4 -2
- package/presentation-advanced-features-demo/README.md +27 -0
- package/presentation-advanced-features-demo/index.html +371 -0
- package/presentation-advanced-features-demo/print.html +81 -0
- package/presentation-advanced-features-demo/slide-01.html +98 -0
- package/presentation-advanced-features-demo/slide-02.html +101 -0
- package/presentation-advanced-features-demo/slide-03.html +100 -0
- package/presentation-advanced-features-demo/slide-04.html +100 -0
- package/presentation-advanced-features-demo/slide-05.html +100 -0
- package/presentation-advanced-features-demo/slide-06.html +64 -0
- package/presentation-advanced-features-demo/slide-07.html +101 -0
- package/presentation-advanced-features-demo/slide-08.html +91 -0
- package/presentation-stella-coder---ai-coding-agent/README.md +27 -0
- package/presentation-stella-coder---ai-coding-agent/index.html +168 -0
- package/presentation-stella-coder---ai-coding-agent/slide-01.html +98 -0
- package/presentation-stella-coder---ai-coding-agent/slide-02.html +101 -0
- package/presentation-stella-coder---ai-coding-agent/slide-03.html +100 -0
- package/presentation-stella-coder---ai-coding-agent/slide-04.html +100 -0
- package/presentation-stella-coder---ai-coding-agent/slide-05.html +100 -0
- package/presentation-stella-coder---ai-coding-agent/slide-06.html +100 -0
- package/presentation-stella-coder---ai-coding-agent/slide-07.html +100 -0
- package/presentation-stella-coder---ai-coding-agent/slide-08.html +64 -0
- package/presentation-stella-coder---ai-coding-agent/slide-09.html +101 -0
- package/presentation-stella-coder---ai-coding-agent/slide-10.html +91 -0
- package/presentation-stella-coder-demo/README.md +27 -0
- package/presentation-stella-coder-demo/index.html +371 -0
- package/presentation-stella-coder-demo/slide-01.html +98 -0
- package/presentation-stella-coder-demo/slide-02.html +101 -0
- package/presentation-stella-coder-demo/slide-03.html +100 -0
- package/presentation-stella-coder-demo/slide-04.html +100 -0
- package/presentation-stella-coder-demo/slide-05.html +100 -0
- package/presentation-stella-coder-demo/slide-06.html +64 -0
- package/presentation-stella-coder-demo/slide-07.html +101 -0
- package/presentation-stella-coder-demo/slide-08.html +91 -0
- package/presentation-stella-coder-features/README.md +27 -0
- package/presentation-stella-coder-features/index.html +364 -0
- package/presentation-stella-coder-features/print.html +77 -0
- package/presentation-stella-coder-features/slide-01.html +98 -0
- package/presentation-stella-coder-features/slide-02.html +101 -0
- package/presentation-stella-coder-features/slide-03.html +100 -0
- package/presentation-stella-coder-features/slide-04.html +100 -0
- package/presentation-stella-coder-features/slide-05.html +64 -0
- package/presentation-stella-coder-features/slide-06.html +101 -0
- package/presentation-stella-coder-features/slide-07.html +91 -0
- package/presentation-test-presentation/README.md +27 -0
- package/presentation-test-presentation/index.html +350 -0
- package/presentation-test-presentation/print.html +69 -0
- package/presentation-test-presentation/slide-01.html +98 -0
- package/presentation-test-presentation/slide-02.html +101 -0
- package/presentation-test-presentation/slide-03.html +64 -0
- package/presentation-test-presentation/slide-04.html +101 -0
- package/presentation-test-presentation/slide-05.html +91 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/README.md +27 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/index.html +159 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-01.html +98 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-02.html +101 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-03.html +100 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-04.html +100 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-05.html +64 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-06.html +101 -0
- package/presentation-/321/202/320/265/321/201/321/202/320/276/320/262/320/260/321/217-/320/277/321/200/320/265/320/267/320/265/320/275/321/202/320/260/321/206/320/270/321/217/slide-07.html +91 -0
- package/stella-cli/coding-brain.mjs +753 -0
- package/stella-cli/index.mjs +931 -15
- package/stella-cli/mcp.mjs +296 -0
- package/stella-cli/presentations.mjs +1106 -0
- package/stella-cli/subagents.mjs +142 -0
- package/stella-cli/telegram-bot.mjs +824 -0
- package/stella-cli/tg-bg.bat +13 -0
- package/stella-cli/tg-server.bat +15 -0
- package/stella-cli/tg-server.mjs +116 -0
- package/stella-cli/theme.mjs +6 -4
- package/stella-coder-3.9.2.tgz +0 -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
|
+
}
|