Археологія Claude Code: як виглядає код з майбутнього
Сьогодні трапився цікавий інцидент — у Anthropic релізнули чергову версію Claude Code з map-файлами, у яких є source код. Невідомо, то помилка людини чи машини, але для нас це влучна нагода заглянути в майбутнє AI-генерованих production-ready проєктів.
Claude Code проєкт надзвичайно цікавий. Почався з хобі-іграшки українського емігранта в США Бориса Черного у серпні 2024 року, на публіку вийшов у лютому 2025 і вже за півроку Anthropic відзвітувала, що проєкт використовує понад 100 тис. розробників по всьому світу.
Компанія особливо підкреслює що це AI-first проєкт, майже весь код згенерований.
Чисто технічно проєкт не без недоліків, є питання і до швидкості роботи, і до споживання пам’яті, і до надійності. Але без сумніву проєкт мегауспішний, зараз ним користуються сотні тисяч розробників і якщо вся Anthropic оцінюється в 380 млрд долл (дані за лютий 2026), то в цих сотнях міль’ярдів як мінімум кілька припадає на Claude Code. Тому якщо хтось почне говорити про те що це якимось чином поганий, неповноцінний, AI-slop і тому подібний проєкт — то попросіть показати його «гарні» проєкти, які коштують мільярди доларів.
То якщо вже нам випала можливість поглянути на «код з майбутнього», то гріх нею не скористатися.
І допоможе нам в цьому Cursor з моделлю від Anthropic Opus 4.6.
Але почнемо ми з того, що поміряємо проєкт за допомогою одного з моїх улюблених інструментів — repomix, і він показує, що в нас близько 2 тис файлів, порядка 31 млн символів і понад 12 млн токенів.
Це насправді показники досить крупного проєкту, не самого великого, з якими доводилося мати справу, але зазвичай проєкти до такого розміру доростають за багато років, отже наша перша знахідка в тому, що темпи його росту по «класичним» міркам значно випереджають прийняті норми «handcrafted» інженерії.
Total Files: 1 902 files Total Tokens: 12 265 233 tokens Total Chars: 30 961 091 chars
Далі переходимо до археології. Я люблю мета-промптинг, закидуємо наступне в чат Cursor:
Я отримав код проєкту, якість коду якого я хочу оцінити. Мені потрібно зрозуміти наскільки добре він зроблений, які в ньому є гарні/погані інженерні рішення, який є технічний борг, ризики і тому подібне. Зроби мені промпт, який я використаю для детального аналізу проєкту.
Далі отриманий промпт закидуємо у нове вікно агента, обираємо модель Opus 4.6 Thinking і поїхали.
Оскільки видачі буде багато і не кожен все пролистає/прочитає, то висновки я вирішив зробити тут, а деталі вже всі полистаєте нижче.
Висновок
Вся ця історія з Claude Code цікава не тим, що там десь знайшли криві місця, захардкоджені промпти чи черговий god-object на кілька тисяч рядків. Цікава вона тим, що перед нами вже не «демка», не «прикольний експеримент» і не «ну от коли-небудь AI навчиться писати справжній продакшн». Він уже пише. Просто цей продакшн виглядає не так, як багато хто собі традиційно його уявляв (і я в тому числі, бо сам є вихованцем традиційної інженерної школи).
Код не стерильний. Не академічно красивий. Не схожий на код з ідеального conference talk про software craftsmanship. У ньому повно компромісів, технічного боргу, дивних рішень та інших слідів шаленого темпу росту.
Але це вже не має принципового значення, бо головний факт у тому, що такі системи працюють, масштабуються, ними користуються сотні тисяч людей і вони створюють бізнес-цінність на мільярди доларів.
Тобто нова нормальність виглядає саме так: AI генерує величезні шматки реального продуктового коду, люди направляють, підчищають, страхують, добудовують архітектуру на ходу, а ринок винагороджує не за естетичну бездоганність, а за швидкість доставки, функціональність і здатність системи виживати під навантаженням. І якщо комусь це не подобається, це не проблема реальності. Це проблема очікувань.
Найцікавіше тут навіть не те, що AI вже може згенерувати великий working codebase. Найцікавіше те, що ми поступово перестаємо міряти такі проєкти старою лінійкою. Бо питання вже не в тому, «чи ідеальний це код». Питання в тому, чи дозволяє такий підхід швидше будувати корисні системи, швидше перевіряти ідеї і швидше захоплювати ринок. І відповідь, схоже, вже отримана.
Майбутнє software engineering, яке ще вчора обговорювали в умовному способі, уже настало. І воно, як це часто буває, прийшло не охайним, не симетричним і не дуже схожим на мрії пуристів. Але саме таким, швидким, місцями костильним, місцями блискучим, страшенно ефективним. Іншими словами, це вже не аномалія. Це вже нова нормальність. Що називається: «Welcome to the future».
Деталі
Промпт
# Глибокий аудит якості коду проєкту ## Контекст Перед тобою вихідний код CLI-застосунку (TypeScript, ~1,884 файли). Стек: Bun runtime, React (термінальний UI через кастомний Ink-подібний рендерер), Commander.js. Структура: плоска (без `src/`), основні директорії — `utils/`, `tools/`, `services/`, `components/`, `commands/`, `ink/`, `bridge/`, `coordinator/`, `tasks/`, `hooks/`, `context/`. Тести: відсутні в цьому снепшоті. Build-конфіги (package.json, tsconfig) також відсутні. ## Завдання Проведи всебічний інженерний аудит кодової бази. Для кожного розділу наводь **конкретні приклади з коду** (назви файлів, функцій, патернів). Не обмежуйся загальними фразами — давай доказову базу. --- ### 1. Архітектура та структура проєкту — Наскільки логічною та послідовною є організація директорій і модулів? — Чи дотримується проєкт якогось архітектурного патерну (layered, hexagonal, plugin-based)? — Як організовані залежності між модулями? Є циклічні залежності? — Чи чітко розділені шари (UI, бізнес-логіка, I/O, інфраструктура)? — Наскільки вдала точка входу (main.tsx) — чи не є вона God Object? — Оціни рішення робити плоску структуру без `src/` — наслідки для навігації та масштабування. ### 2. Якість коду та читабельність — Консистентність стилю коду (нейменг, форматування, імпорти). — Розмір файлів і функцій — є занадто великі? Наведи топ-10 найбільших файлів та оціни, чи виправдано їх розмір. — Якість абстракцій: чи добре названі класи/функції/типи? Чи розкривають вони intent? — Рівень дублювання коду (copy-paste патерни). — Чи використовуються magic numbers/strings? Наскільки виносяться константи? — Якість коментарів: інформативні чи шумові? Є TODO/FIXME/HACK — скільки і про що? ### 3. Типізація та TypeScript-практики — Наскільки строго типізований код? Є `any`, `as` type assertions, `@ts-ignore`? — Якість інтерфейсів та типів: чи гранулярні вони, чи монолітні? — Використання дженериків — адекватне чи надмірне? — Чи використовуються discriminated unions, branded types, utility types де доречно? — Наявність runtime type validation (zod, io-ts, тощо) на межах системи ### 4. Обробка помилок та надійність — Стратегія обробки помилок: чи є єдиний підхід? — Чи ловляться помилки на правильному рівні? Є «проковтані» помилки (empty catch)? — Якість error messages — чи допомагають вони діагностувати проблему? — Graceful degradation: як система поводиться при збоях зовнішніх залежностей? — Чи є retry-логіка, circuit breakers, таймаути де потрібно? ### 5. Безпека — Як обробляються секрети, токени, API-ключі? — Чи є санітизація вводу (особливо для shell-команд у Bash tool)? — Permission model: як реалізований, наскільки надійний? — OAuth flow: чи коректна реалізація? — Чи є потенційні вразливості (injection, path traversal, SSRF)? ### 6. Стан та управління даними — Як управляється стан застосунку (React state, глобальний стан, файловий стан)? — Чи є race conditions в асинхронному коді? — Як реалізований кеш? Є стратегія інвалідації? — Серіалізація/десеріалізація: як зберігаються налаштування, сесії? ### 7. Продуктивність — Є очевидні performance bottlenecks? — Як обробляються великі файли/довгі виводи? — Memory leaks: чи правильно очищаються ресурси, listeners, subscriptions? — Конкурентність: як обробляються паралельні операції (multi-agent, MCP)? ### 8. Тестованість — Наскільки код піддається юніт-тестуванню у поточному стані? — Dependency injection vs hard-coded dependencies. — Чи можна мокати зовнішні залежності (API, файлова система, shell)? — Які частини коду найважче тестувати і чому? ### 9. Технічний борг — Складіть конкретний реєстр технічного боргу з пріоритетами (critical / high / medium / low). — Для кожного елемента: опис, де саме в коді, оцінка ризику, приблизна складність виправлення. — Виділи борг, який блокує розвиток vs борг, з яким можна жити. ### 10. Паттерни проєктування — Які Design Patterns використані? Чи доречно? — Які патерни відсутні, але були б корисні? — Антипатерни: God Objects, Feature Envy, Shotgun Surgery, тощо — з прикладами. ### 11. Сильні сторони — Що зроблено добре? Які інженерні рішення варті наслідування? — Вдалі абстракції, елегантні рішення, продумані API. — Що свідчить про досвідчену команду? ### 12. Ризики — Які найбільші технічні ризики для продакшену? — Що може зламатися при масштабуванні? — Які зміни в зовнішніх залежностях можуть стати критичними? — Bus factor: чи є частини коду, які розуміє тільки одна людина? --- ## Формат відповіді Для кожного розділу: 1. **Загальна оцінка** (1-10 балів з коротким обґрунтуванням) 2. **Знахідки** — конкретні приклади з коду (файл, рядок, фрагмент) 3. **Рекомендації** — що і як покращити, з пріоритетом На завершення дай: — **Зведену таблицю оцінок** по всіх розділах — **Загальну оцінку зрілості** проєкту (prototype / MVP / production-ready / enterprise-grade) — **Топ-5 найкритичніших проблем**, які потрібно вирішити першими — **Топ-5 найсильніших рішень**, які варто зберегти та розвивати
Результат
Глибокий аудит якості коду проєкту Claude Code CLI
1. Архітектура та структура проєкту
Загальна оцінка: 6/10
Знахідки
Організація директорій — проєкт використовує плоску структуру без src/, з ~25 top-level директоріями: utils/ (564 файли), components/ (389), commands/ (207), tools/ (184), services/ (130), hooks/ (100+), ink/ (96), bridge/ (31), constants/ (21), skills/ (21), keybindings/ (14), tasks/ (12), context/ (9), state/ (6), screens/ (3), coordinator/ (1), та інші.
Архітектурний патерн — де-факто layered з елементами plugin-based архітектури:
- UI шар:
screens/,components/,ink/(кастомний Ink-подібний рендерер) - Команди:
commands/(CLI subcommands через Commander.js) - Бізнес-логіка:
tools/,services/,utils/,query/ - Інфраструктура:
bridge/,entrypoints/,state/
Однак межі шарів розмиті. utils/ є «catch-all» директорією із 564 файлами, що містить і бізнес-логіку (utils/permissions/, utils/plugins/, utils/swarm/), і інфраструктуру (utils/telemetry/, utils/secureStorage/), і хелпери (utils/format.ts, utils/array.ts).
Точка входу main.tsx — 4,684 рядків, з них функція run() займає ~3,600 рядків
// main.tsx, Lines 884-903
async function run(): Promise<CommanderCommand> {
profileCheckpoint('run_function_start');
function createSortedHelpConfig(): {
sortSubcommands: true;
sortOptions: true;
} {
// ...
}
const program = new CommanderCommand()
.configureHelp(createSortedHelpConfig())
.enablePositionalOptions();
Плоска структура без src/ — це створює кілька проблем:
- Root директорія перевантажена 6 файлами + 25+ директорій
- Змішування runtime-коду з конфігурацією ускладнює навігацію
- imports використовують як
./, так іsrc/path alias паралельно, що вносить непослідовність
Циклічні залежності — проєкт активно бореться з ними через require() замість import:
// permissions.ts, Lines 59-60
const classifierDecisionModule = feature('TRANSCRIPT_CLASSIFIER')
? (require('./classifierDecision.js') as typeof import('./classifierDecision.js'))
Це свідчить про усвідомлену проблему циклічних залежностей і pragmatic workaround.
Рекомендації
- Високий пріоритет: Розбити
main.tsx:run()на окремі модулі (cli/programDefinition.ts,cli/preActionInit.ts,cli/defaultCommandHandler.ts) - Середній: Реструктурувати
utils/— перенестиpermissions/,plugins/,swarm/у top-level абоdomain/директорію - Низький: Запровадити
src/як корінь вихідного коду для відокремлення від конфігурації
2. Якість коду та читабельність
Загальна оцінка: 5/10
Знахідки
Топ-10 найбільших файлів:
| Файл | Рядків | Обґрунтування розміру |
|---|---|---|
utils/messages.ts | 5,512 | Невиправдано: фабрики повідомлень можна розбити по типах |
utils/sessionStorage.ts | 5,105 | Невиправдано: змішує persistence + querying + migration |
utils/hooks.ts | 5,022 | Невиправдано: «catch-all» для git/shell хуків |
screens/REPL.tsx | 5,006 | Критично: God Component з 68 useState, ~80 імпортами |
main.tsx | 4,684 | Невиправдано: god module (розбирався вище) |
utils/plugins/pluginLoader.ts | 3,302 | Потребує декомпозиції |
utils/plugins/marketplaceManager.ts | 2,643 | На межі допустимого |
tools/BashTool/bashSecurity.ts | 2,593 | Частково виправдано складністю домену безпеки |
utils/shell/readOnlyCommandValidation.ts | 1,893 | Великі lookup-таблиці — можна виділити |
utils/powershell/parser.ts | 1,804 | Парсер — складність обґрунтована |
God Component REPL.tsx — це найсерйозніша проблема читабельності:
// REPL.tsx, Lines 572-598
export function REPL({
commands: initialCommands,
debug,
initialTools,
initialMessages,
pendingHookMessages,
initialFileHistorySnapshots,
initialContentReplacements,
initialAgentName,
initialAgentColor,
mcpClients: initialMcpClients,
dynamicMcpConfig: initialDynamicMcpConfig,
autoConnectIdeFlag,
strictMcpConfig = false,
systemPrompt: customSystemPrompt,
appendSystemPrompt,
onBeforeQuery,
onTurnComplete,
disabled = false,
mainThreadAgentDefinition: initialMainThreadAgentDefinition,
disableSlashCommands = false,
taskListId,
remoteSessionConfig,
directConnectConfig,
sshSession,
thinkingConfig
}: Props): React.ReactNode {
Один компонент із 68 useState, ~80 імпортів, ~4,430 рядків функціонального тіла.
Нейменг та стиль — загалом консистентний:
camelCaseдля функцій/зміннихPascalCaseдля React компонентів та типів- Файли інструментів у окремих директоріях (
tools/BashTool/BashTool.ts)
Magic strings — "external" === 'ant' зустрічається 82+ разів у 27+ файлах. Це механізм dead code elimination для build variants, але відсутність Named Helper (наприклад isAntBuild()) робить код непрозорим:
// main.tsx, Lines 265-271
if ("external" !== 'ant' && isBeingDebugged()) {
process.exit(1);
}
TODO коментарі — 88 штук, 2 HACK, 0 FIXME. Приклади:
screens/REPL.tsx— кілька TODOentrypoints/mcp.ts— «TODO: validate input with Zod»utils/hooks.ts— множинні TODO
Коментарі — якість висока для критичних модулів: subprocessEnv.ts, gracefulShutdown.ts, path.ts мають детальні пояснення «чому» а не «що». Це сильна сторона.
Рекомендації
- Критичний: Декомпонувати
REPL.tsxна під-компоненти з custom hooks - Високий: Виділити
"external" === 'ant'у helper-функціюisInternalBuild()або const - Середній: Розбити
utils/messages.tsтаutils/sessionStorage.tsна менші модулі
3. Типізація та TypeScript-практики
Загальна оцінка: 8/10
Знахідки
Суворість типізації — проєкт демонструє зрілий підхід:
any: лише ~35: any(більшість у коментарях або генерованому коді), ~10as any(2 — false positives)@ts-ignore: 0 — абсолютно відсутній@ts-expect-error: 13 — усі уink/(сумісність з react-reconciler типами) таupstreamproxy/relay.ts(Bun WebSocket)- Type assertions
as: ~631 occurrence — помірний рівень, частина з нихas const
Zod runtime validation — використовується масово (~130+ файлів):
// deps.ts, Lines 21-31
export type QueryDeps = {
callModel: typeof queryModelWithStreaming
microcompact: typeof microcompactMessages
autocompact: typeof autoCompactIfNeeded
uuid: () => string
}
Discriminated unions — правильно використовуються:
vim/types.ts—type: 'idle' | 'count' | 'operator' | …utils/plugins/schemas.ts—z.discriminatedUnion('source', …)utils/permissions/PermissionUpdateSchema.ts—z.discriminatedUnion('type', …)
Branded types — присутні, але обмежено (3 визначення):
types/ids.ts—SessionId,AgentIdз__brandutils/systemPromptType.ts—SystemPrompt
Телеметрія з типобезпечністю — цікавий патерн із типом-маркером:
// messages.ts, Lines 19-22
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from 'src/services/analytics/index.js'
Назва типу AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS — це «human-in-the-loop» type assertion, що змушує розробника підтвердити, що передані дані не містять чутливої інформації. Оригінальний підхід.
Рекомендації
- Середній: Розширити branded types для інших domain identifiers (
ToolId,PluginId) - Низький: Замінити
@ts-expect-errorвink/на proper type declarations для react-reconciler - Низький: Зменшити кількість
asassertions — деякі можна замінити type guards
4. Обробка помилок та надійність
Загальна оцінка: 7/10
Знахідки
Структурований підхід — проєкт використовує кілька шарів обробки помилок:
utils/errors.ts— базові утиліти (AbortError,toError,errorMessage)utils/gracefulShutdown.ts— централізований shutdown із обробкою сигналів:
// gracefulShutdown.ts, Lines 59-62
function cleanupTerminalModes(): void {
if (!process.stdout.isTTY) {
return
}
- Typed errors:
DirectConnectError,TeleportOperationErrorзformattedMessage
«Проковтані» помилки:
- 2 повністю порожніх
catch {}(FullscreenLayout.tsx:485,PluginSettings.tsx:388) - ~57
.catch(() => {})— fire-and-forget promise handlers - Деякі justified (cleanup, analytics), але відсутній логінг у більшості:
# Файли з .catch(() => {}) — ніякого логування:
bridge/replBridge.ts — 6 випадків
services/mcp/client.ts — 5 випадків
main.tsx — 5 випадків
utils/computerUse/computerUseLock.ts — 3 випадків
Retry-логіка — реалізована де потрібно:
utils/teleport/api.ts— exponential backoff[2000, 4000, 8000, 16000]utils/plugins/officialMarketplaceStartupCheck.ts— retry config зBACKOFF_MULTIPLIER: 2utils/ripgrep.ts— EAGAIN retry з зменшенням паралелізму (-j 1)services/api/withRetry— загальна retry-обгортка для API
Circuit breakers — елегантна реалізація:
- Auto mode:
autoModeCircuitBrokenуutils/permissions/autoModeState.ts - Autocompact:
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURESуservices/compact/autoCompact.ts
Graceful shutdown — всебічна реалізація:
// gracefulShutdown.ts, Lines 1-4
import chalk from 'chalk'
import { writeSync } from 'fs'
import memoize from 'lodash-es/memoize.js'
import { onExit } from 'signal-exit'
Обробляє SIGINT/SIGTERM/SIGHUP, orphan TTY detection, terminal mode cleanup, uncaughtException/unhandledRejection.
Рекомендації
- Високий: Замінити
catch () {}та.catch(() => {})на.catch(logError)або.catch(noop)з документованою причиною - Середній: Створити utility
safeAsync()для fire-and-forget з опціональним логуванням - Низький: Стандартизувати error types через базовий
AppErrorклас
5. Безпека
Загальна оцінка: 9/10
Знахідки
Це найсильніша сторона проєкту. Security practices виключно зрілі.
Subprocess env scrubbing — utils/subprocessEnv.ts видаляє 20+ змінних оточення з child processes:
// subprocessEnv.ts, Lines 15-53 const GHA_SUBPROCESS_SCRUB = [ 'ANTHROPIC_API_KEY', 'CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_AUTH_TOKEN', // ... 'ACTIONS_ID_TOKEN_REQUEST_TOKEN', 'ACTIONS_RUNTIME_TOKEN', 'ALL_INPUTS', 'SSH_SIGNING_KEY', ] as const
Bash command sanitization — tools/BashTool/bashSecurity.ts (2,593 рядків) — глибокий defense-in-depth:
- Блокування command substitution:
$(),${}, process substitution<(),>() - Захист від Zsh-специфічних атак:
zmodload,emulate -c,=cmdexpansion - PowerShell comment syntax як defense-in-depth
// bashSecurity.ts, Lines 16-41
const COMMAND_SUBSTITUTION_PATTERNS = [
{ pattern: /<\(/, message: 'process substitution <()' },
{ pattern: />\(/, message: 'process substitution >()' },
{ pattern: /=\(/, message: 'Zsh process substitution =()' },
{ pattern: /\$\(/, message: '$() command substitution' },
{ pattern: /\$\{/, message: '${} parameter substitution' },
// ...
]
Path traversal protection — багаторівнева:
utils/path.ts:containsPathTraversal()— regex для..сегментівutils/worktree.ts:validateWorktreeSlug()— посегментна валідація- UNC path detection для запобігання NTLM credential leaks:
// path.ts, Lines 111-113
if (absolutePath.startsWith('\\\\') || absolutePath.startsWith('//')) {
return dirname(absolutePath)
}
Null byte injection protection:
// path.ts, Lines 47-49
if (path.includes('\0') || actualBaseDir.includes('\0')) {
throw new Error('Path contains null bytes')
}
OAuth — PKCE flow з state parameter для CSRF protection, allowlisted OAuth URLs:
// oauth.ts, Lines 179-183 const ALLOWED_OAUTH_BASE_URLS = [ 'beacon.claude-ai.staging.ant.dev', 'claude.fedstart.com', 'claude-staging.fedstart.com', ]
Windows security — NoDefaultCurrentDirectoryInExePath для запобігання PATH hijacking:
// main.tsx, Lines 590-591 process.env.NoDefaultCurrentDirectoryInExePath = '1';
Телеметрія — тип AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS як guard проти витоку коду/шляхів у аналітику. URL credentials redaction у plugin logging.
Рекомендації
- Низький: Додати CSP headers для будь-яких HTTP серверів (direct connect)
- Низький: Розглянути rate limiting для localhost OAuth callback listener
6. Стан та управління даними
Загальна оцінка: 6/10
Знахідки
React state — гібридний підхід:
state/store.ts — мінімалістичний external store (35 рядків) з useSyncExternalStore:
// store.ts, Lines 10-34
export function createStore<T>(
initialState: T,
onChange?: OnChange<T>,
): Store<T> {
let state = initialState
const listeners = new Set<Listener>()
return {
getState: () => state,
setState: (updater: (prev: T) => T) => {
const prev = state
const next = updater(prev)
if (Object.is(next, prev)) return
state = next
onChange?.({ newState: next, oldState: prev })
for (const listener of listeners) listener()
},
subscribe: (listener: Listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
REPL.tsx додатково використовує 68 useState hooks — це антипатерн, state слід консолідувати.
Module-level mutable state — сотні let змінних на рівні модулів:
utils/tmuxSocket.ts— 7 module-level let (socketName,socketPath,serverPid,isInitializing,initPromise...)utils/user.ts—cachedEmail,emailFetchPromiseutils/config.ts—globalConfigCacheutils/fastMode.ts—runtimeState,inflightPrefetch
Race conditions — потенційні ризики:
// user.ts, Lines 53-60
export async function initUser(): Promise<void> {
if (cachedEmail === null && !emailFetchPromise) {
emailFetchPromise = getEmailAsync()
cachedEmail = await emailFetchPromise
emailFetchPromise = null
getCoreUserData.cache.clear?.()
}
}
Check-then-act без синхронізації — якщо два concurrent callers перевірять emailFetchPromise === null одночасно, обидва створять окремі запити.
Cache — ParsedCommand.ts з size-1 кешем:
// ParsedCommand.ts, Lines 297-308
let lastCmd: string | undefined
let lastResult: Promise<IParsedCommand | null> | undefined
// ...
parse(command: string): Promise<IParsedCommand | null> {
if (command === lastCmd && lastResult !== undefined) {
return lastResult
}
lastCmd = command
lastResult = doParse(command)
return lastResult
},
Concurrent parse з різними командами перезапише кеш — потенційний data race.
Signal primitive — елегантне рішення для pub/sub:
// signal.ts, Lines 27-43
export function createSignal<Args extends unknown[] = []>(): Signal<Args> {
const listeners = new Set<(...args: Args) => void>()
return {
subscribe(listener) {
listeners.add(listener)
return () => { listeners.delete(listener) }
},
emit(...args) {
for (const listener of listeners) listener(...args)
},
clear() { listeners.clear() },
}
}
Рекомендації
- Високий: Консолідувати 68
useStateвREPL.tsxчерезuseReducerабо dedicated hooks - Середній: Додати mutex/lock patterns для inflight promise caches (
user.ts,fastMode.ts) - Низький: Документувати threading model для module-level state
7. Продуктивність
Загальна оцінка: 7/10
Знахідки
Startup performance — продумана оптимізація:
Import-time side effects для паралельних subprocess запусків:
// main.tsx, Lines 1-20 // These side-effects must run before all other imports: // 1. profileCheckpoint marks entry before heavy module evaluation begins // 2. startMdmRawRead fires MDM subprocesses (plutil/reg query) so they run in // parallel with the remaining ~135ms of imports below // 3. startKeychainPrefetch fires both macOS keychain reads ...
profileCheckpoint та profileReport для моніторингу startup latency. preAction hook для відкладеної ініціалізації.
Lazy loading — feature flags через bun:bundle з dead code elimination:
// AppState.tsx, Lines 14-18
const VoiceProvider: (props: {
children: React.ReactNode;
}) => React.ReactNode = feature('VOICE_MODE')
? require('../context/voice.js').VoiceProvider
: ({ children }) => children;
React Compiler — проєкт використовує React Compiler для автоматичної мемоїзації:
// REPL.tsx, Line 1
import { c as _c } from "react/compiler-runtime";
Потенційні bottlenecks:
REPL.tsxз 68useState— коженsetStateможе викликати re-rendercommands/insights.ts:generateHtmlReport(~730 рядків) — побудова HTML рядка в пам’яті
Env-var checks на render path оптимізовані через useMemo:
// REPL.tsx, Lines 601-604 const titleDisabled = useMemo( () => isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_TERMINAL_TITLE), [] ); const moreRightEnabled = useMemo( () => "external" === 'ant' && isEnvTruthy(process.env.CLAUDE_MORERIGHT), [] );
Рекомендації
- Середній: Профілювати re-renders у
REPL.tsx— 68useStateможуть спричиняти cascading updates - Низький: Розглянути streaming для
generateHtmlReportзамість in-memory string building
8. Тестованість
Загальна оцінка: 5/10
Знахідки
Dependency Injection — паттерн QueryDeps є зразком, але обмежений одним модулем:
// deps.ts, Lines 8-20 // I/O dependencies for query(). Passing a `deps` override into QueryParams // lets tests inject fakes directly instead of spyOn-per-module — the most // common mocks (callModel, autocompact) are each spied in 6-8 test files // today with module-import-and-spy boilerplate. // // Scope is intentionally narrow (4 deps) to prove the pattern. Followup // PRs can add runTools, handleStopHooks, logEvent, queue ops, etc.
Коментар підтверджує, що це «proving the pattern» з планом розширення.
Hard-coded dependencies — домінуючий підхід:
- Більшість модулів використовують статичні imports
- Module-level singletons (
getInstance(), module-scopelet) ускладнюють ізоляцію process.envчитається безпосередньо — mock вимагає глобальної підміни
Найскладніше тестувати:
REPL.tsx— 68 internal states, 80+ imports, 4,430 linesmain.tsx:run()— 3,600 lines з side effects- Модулі з module-scope state (
tmuxSocket.ts,user.ts,config.ts) — потребують reset між тестами bashSecurity.ts— складна tree-sitter аналіз коду
Позитив: Деякі утиліти чудово тестуються: store.ts (35 рядків, pure function), signal.ts (44 рядки), path.ts (pure transforms).
Рекомендації
- Високий: Розширити
QueryDepsпаттерн на tools, services, MCP client - Середній: Виділити module-scope state у injectable stores
- Середній: Створити test utilities для reset module-level state
9. Технічний борг
Реєстр технічного боргу:
| # | Пріоритет | Опис | Де | Ризик | Складність |
|---|---|---|---|---|---|
| 1 | Critical | God Component REPL.tsx — 5,006 lines, 68 useState | screens/REPL.tsx | Блокує розвиток UI, maintainability near zero | Висока (тижні) |
| 2 | Critical | God Module main.tsx:run() — 3,600 lines | main.tsx:884-4513 | Будь-яка зміна CLI потребує навігації по 3,600 рядках | Висока |
| 3 | High | 57+ .catch(() => {}) — проковтування помилок | 38+ файлів | Невидимі збої в production | Середня |
| 4 | High | 82+ «external» === ’ant’ без абстракції | 27+ файлів | Shotgun Surgery при зміні build variant logic | Низька |
| 5 | High | utils/ як catch-all (564 файли) | utils/** | Навігація O(n), розмиті межі модулів | Висока |
| 6 | Medium | 88 TODO коментарів без трекінгу | Різні файли | Забуті задачі | Низька |
| 7 | Medium | Race conditions у inflight promise caches | user.ts, fastMode.ts, ParsedCommand.ts | Рідкісні баги при concurrent access | Середня |
| 8 | Medium | Дублювання import ./bootstrap/state.js | main.tsx:87,168 | Confusion, minor | Мінімальна |
| 9 | Low | useReducer лише у 3 файлах, 6 usage | Весь проєкт | useState sprawl у складних компонентах | Середня |
| 10 | Low | Обмежений DI (лише QueryDeps) | query/deps.ts | Ускладнене тестування | Висока |
Борг, що блокує розвиток: #1, #2, #5
Борг, з яким можна жити: #6, #8, #9
10. Паттерни проєктування
Загальна оцінка: 7/10
Використані паттерни
| Паттерн | Де | Оцінка |
|---|---|---|
| Composition Root | main.tsx | Доречно, але overloaded |
| Observer/Signal | utils/signal.ts | Елегантно, замінює 15× дублювання |
| External Store | state/store.ts + useSyncExternalStore | Сучасний, мінімалістичний |
| Feature Flag / DCE | bun:bundle feature() | Effective build-time elimination |
| Singleton | DiagnosticTrackingService, ActivityManager, AwsAuthStatusManager | Стандартно |
| Strategy | Permission rules pipeline | Добре розділяє правила та execution |
| Plugin System | plugins/, skills/, commands/ | Extensible architecture |
| Circuit Breaker | Auto mode, autocompact | Production-grade resilience |
| PKCE OAuth | services/oauth/ | Industry standard |
| DI (prototype) | query/deps.ts | Правильний напрямок, обмежений scope |
Антипатерни
| Антипатерн | Приклад | Severity |
|---|---|---|
| God Object/Module | REPL.tsx, main.tsx:run() | Critical |
| Shotgun Surgery | "external" === 'ant' у 27+ файлах | High |
| Feature Envy | main.tsx знає деталі teleport, plugins, MCP, SSH, etc. | High |
| Primitive Obsession | Module-level let замість encapsulated state objects | Medium |
| Swiss Army Knife | utils/ з 564 файлами | Medium |
Відсутні, але корисні паттерни
- Mediator — для координації між REPL state slices
- Repository — для
sessionStorage.ts(5,105 lines без чіткого data access layer) - Builder — для складних system prompt / message construction
11. Сильні сторони
Загальна оцінка: 8/10
Що зроблено добре
Security-first мислення — bash sanitization, env scrubbing, path traversal protection, UNC detection, null byte validation, PKCE OAuth, allowlisted URLs — це enterprise-grade security:
// path.ts, Lines 47-49
if (path.includes('\0') || actualBaseDir.includes('\0')) {
throw new Error('Path contains null bytes')
}
createSignal() primitive — елегантне рішення, що замінило 15× дублювання boilerplate. Типізований, мінімалістичний, з clear API.
createStore() + useSyncExternalStore — 35 рядків коду замість Redux/Zustand. Ідеальний баланс simplicity/power для CLI додатку.
Startup optimization — паралельні subprocess launches під час import evaluation, профілювання checkpoint-ами, lazy loading через feature flags. Демонструє глибоке розуміння Node.js/Bun startup performance.
QueryDeps pattern — правильний DI підхід з typeof для автоматичної синхронізації типів. Документація чітко пояснює мотивацію та scope.
AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS — креативний «human gate» тип, що вимагає свідомого рішення розробника перед відправкою даних у телеметрію.
Graceful shutdown — comprehensive: terminal mode cleanup (Kitty keyboard, mouse tracking, alt screen), telemetry flush, orphan TTY detection, signal handlers. Враховує edge cases типу tmux/screen.
Zod-first validation — ~130+ файлів використовують Zod для runtime validation на межах системи (API, CLI args, config, plugin schemas).
Detailed comments — критичні модулі (subprocessEnv.ts, gracefulShutdown.ts, path.ts, oauth/client.ts) мають якісні пояснення мотивації, а не просто опис що робить код.
Circuit breaker implementation — auto mode + autocompact circuit breakers з proper counting та reset logic.
12. Ризики
Загальна оцінка: 6/10
Найбільші технічні ризики
- REPL.tsx як single point of failure — будь-який баг у
5,006-рядковому компоненті ламає весь UI. Refactoring ризикований через coupling між 68 state variables. - Module-level mutable state — сотні
letзмінних без формальної ownership моделі. При масштабуванні (multi-agent, concurrent sessions) race conditions стануть частішими. - Bun runtime lock-in —
import { feature } from 'bun:bundle',bun:bundleside effects, React Compiler runtime — все це прив’язує до Bun. Міграція на Node.js або Deno потребуватиме значних зусиль. - bashSecurity.ts surface area — 2,593 рядки security-critical коду для блокування shell injection. Кожна нова shell, нова версія bash/zsh може ввести bypass. Потребує continuous fuzzing.
- Bus factor —
main.tsx:run()(3,600 lines),REPL.tsx(5,006 lines),bashSecurity.ts(2,593 lines) — ці файли настільки великі, що лише автори повністю розуміють всі flow branches. - Зовнішні залежності:
@anthropic-ai/sdk— зміни API можуть зламатиservices/api/claude.tsreact-reconciler— кастомний Ink renderer залежить від internal React API- Зміни у Commander.js TypeScript typings (вже є workaround з
Object.assign)
Зведена таблиця оцінок
| Розділ | Оцінка | Коментар |
|---|---|---|
| 1. Архітектура | 6/10 | God modules, розмита utils/, але продумана plugin system |
| 2. Якість коду | 5/10 | Файли-гіганти, God Component REPL.tsx, але хороші коментарі |
| 3. TypeScript | 8/10 | Майже 0 any, Zod validation, branded types |
| 4. Обробка помилок | 7/10 | Circuit breakers, retry, але 57+ swallowed errors |
| 5. Безпека | 9/10 | Enterprise-grade: env scrubbing, bash sanitization, PKCE |
| 6. Стан | 6/10 | Хороший store pattern, але 68 useState, race conditions |
| 7. Продуктивність | 7/10 | Startup optimization, React Compiler, lazy loading |
| 8. Тестованість | 5/10 | Module singletons, обмежений DI, god components |
| 9. Технічний борг | 5/10 | 3 critical items, 88 TODOs, shotgun surgery pattern |
| 10. Паттерни | 7/10 | Signal, Store, Circuit Breaker — але God Object антипатерн |
| 11. Сильні сторони | 8/10 | Security, startup perf, signal primitive, Zod |
| 12. Ризики | 6/10 | Bun lock-in, bus factor, REPL SPOF |
Середня оцінка: 6.6/10
Загальна оцінка зрілості
MVP+ / Early Production — проєкт знаходиться між MVP та production-ready. Security-практики на рівні enterprise-grade, але архітектурні проблеми (god modules, тестованість, state management) є типовими для швидко зростаючого проєкту, що prioritizes features over refactoring.
Топ-5 найкритичніших проблем
- God Component REPL.tsx (5,006 lines, 68 useState) — потребує декомпозиції на
10-15 під-компонентів з custom hooks. Блокує розвиток UI. - God Module main.tsx:run() (3,600 lines) — потребує розбиття на CLI definition, init pipeline, feature handlers. Блокує розвиток CLI.
- 57+ проковтаних помилок (
.catch(() => {})) — потенційно приховує production-баги, особливо уbridge/та MCP client. - Shotgun Surgery: «external» === ’ant’ (82+ використань у 27+ файлах) — кожна зміна build variant логіки потребує правок у десятках файлів.
- utils/ як catch-all (564 файли) — розмиває межі відповідальності модулів, ускладнює навігацію та рефакторинг.
Топ-5 найсильніших рішень
- Security architecture — багаторівнева система: subprocess env scrubbing, bash command sanitization (2,593 lines defense-in-depth), path traversal protection, null byte validation, UNC detection, PKCE OAuth із allowlisted URLs. Це best-in-class для CLI інструменту.
createSignal<Args>()primitive — 44 рядки коду, що замінили 15× дублювання pub/sub boilerplate. Типізований, мінімалістичний, з subscribe/emit/clear API.createStore<T>()+useSyncExternalStore— 35 рядків мінімального external store замість важких state management бібліотек.Object.isequality, functional updates, listener cleanup — все необхідне без зайвого.- Startup performance optimization — паралельні subprocess launches (MDM, keychain) під час import evaluation;
profileCheckpointдля трекінгу;preActionhook для відкладеної ініціалізації; React Compiler для auto-memoization. - QueryDeps +
AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS— два зразки software craftsmanship: explicit DI зtypeofдля type-safety, та «human gate» тип для запобігання витоку чутливих даних у телеметрію.
Про prompt-менеджмент
В інтернетах вже встигли покритикувати захардкоджені промпти на стороні клієнта, але я скажу що з цим не все так однозначно, і для підтримки цієї думки теж запитав Cursor.
Як можна прокоментувати prompt-менеджмент у цьому проєкті?
Чи не було б доречно тримати промпти на сервері, а з клієнта лише викликати їх по ідентифікаторам?
Коротка відповідь
В загальному випадку — так, серверні промпти мають вагомі переваги. Для цього конкретного проєкту — ні, поточна клієнтська модель є більш обґрунтованою, і ось чому.
Два варіанти серверного підходу
Спочатку визначимо, що саме означає «тримати промпти на сервері»:
Варіант A — Prompt Registry: клієнт запитує текст промпту по ID, отримує його, потім сам відправляє в Anthropic API:
Client → Prompt Server: GET /prompts/system-v42?tools=bash,grep&lang=uk
Prompt Server → Client: { text: "You are an interactive agent..." }
Client → Anthropic API: { system: text, messages: [...] }
Варіант B — Thin Client: клієнт взагалі не бачить prompt, сервер-проксі збирає prompt і викликає API:
Client → Proxy: { promptId: "system-v42", context: {...}, messages: [...] }
Proxy: assembles prompt + calls Anthropic API
Proxy → Client: { response: "..." }
Обидва варіанти мають фундаментальні проблеми для
Чому серверний підхід не підходить саме тут
1. Промпти глибоко переплетені з runtime-станом клієнта
Подивімося, від чого залежить збирання system prompt у constants/prompts.ts:
// prompts.ts, Lines 444-461
export async function getSystemPrompt(
tools: Tools,
model: string,
additionalWorkingDirectories?: string[],
mcpClients?: MCPServerConnection[],
): Promise<string[]> {
// ...
const cwd = getCwd()
const [skillToolCommands, outputStyleConfig, envInfo] = await Promise.all([
getSkillToolCommands(cwd),
getOutputStyleConfig(),
computeSimpleEnvInfo(model, additionalWorkingDirectories),
])
const settings = getInitialSettings()
const enabledTools = new Set(tools.map(_ => _.name))
Prompt залежить від:
| Джерело даних | Де знаходиться | Можна передати на сервер? |
|---|---|---|
| Доступні tools (tools, MCP servers) | Клієнт (динамічно підключені MCP) | Теоретично, але лише імена |
getCwd(), platform, shell, OS | Клієнт | Так, але ~15 полів |
| CLAUDE.md / rules / memory hierarchy | Файлова система клієнта | Ні — це приватні файли користувача |
| Git status, branch, recent commits | Локальний git repo | Ні |
| Settings (language, output style) | Локальний конфіг | Так |
Feature flags (feature(), GrowthBook) | Build-time + runtime | Частково |
| Plugin/skill definitions | Локально встановлені плагіни | Ні |
| Permissions mode | Клієнт | Так |
| Worktree session state | Клієнт | Ні |
| Proactive/coordinator mode | Клієнт | Частково |
Щоб сервер зібрав prompt, клієнт мусив би серіалізувати і передати весь свій контекст — десятки полів. По суті, ви б передавали на сервер усі вхідні дані для функції getSystemPrompt(), а сервер виконував би ту саму логіку. Це не спрощення — це перенос складності з одного місця в інше плюс мережевий hop.
2. CLAUDE.md — це приватний user content
Ієрархія memory files:
// claudemd.ts, Lines 1-9 /** * Files are loaded in the following order: * * 1. Managed memory (eg. /etc/claude-code/CLAUDE.md) - Global instructions * 2. User memory (~/.claude/CLAUDE.md) - Private global instructions * 3. Project memory (CLAUDE.md, .claude/CLAUDE.md, .claude/rules/*.md) * 4. Local memory (CLAUDE.local.md) - Private project-specific instructions */
Файли .claude/CLAUDE.md та CLAUDE.local.md — це приватні інструкції користувача, які ніколи не повинні покидати його машину (окрім як у API-запиті до Anthropic). Серверний prompt registry повинен був би або ігнорувати цей шар, або отримувати ці файли — обидва варіанти погані.
3. Tool prompts коло-розташовані з кодом tool — і це правильно
Кожен tool має свій prompt.ts, який описує tool для моделі:
tools/BashTool/prompt.ts → залежить від sandbox mode, timeouts, git settings tools/PowerShellTool/prompt.ts → async! залежить від shell discovery tools/AgentTool/prompt.ts → залежить від available subagent types tools/FileReadTool/prompt.ts → залежить від file ignore patterns
Ці промпти семантично пов’язані з implementation code tool. Якщо BashTool додає новий параметр run_in_background, prompt повинен описати його у тому ж PR. Розділення їх на «клієнтський код» і «серверний prompt» створює version skew: клієнт v2.5 з run_in_background може отримати prompt від сервера, який ще не знає про цей параметр.
4. Prompt caching architecture спеціально оптимізована під клієнтську збірку
Поточна архітектура з SYSTEM_PROMPT_DYNAMIC_BOUNDARY дозволяє Anthropic API кешувати статичну частину prompt:
// prompts.ts, Lines 560-576 return [ // --- Static content (cacheable) --- getSimpleIntroSection(outputStyleConfig), getSimpleSystemSection(), // ... // === BOUNDARY MARKER === ...(shouldUseGlobalCacheScope() ? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []), // --- Dynamic content (registry-managed) --- ...resolvedDynamicSections, ].filter(s => s !== null)
А DANGEROUS_uncachedSystemPromptSection() вимагає обґрунтування для cache-breaking:
// systemPromptSections.ts, Lines 32-38
export function DANGEROUS_uncachedSystemPromptSection(
name: string,
compute: ComputeFn,
_reason: string,
): SystemPromptSection {
return { name, compute, cacheBreak: true }
}
Ця оптимізація має сенс тільки коли клієнт контролює структуру prompt і знає, що «ця частина однакова для всіх» (global cache) vs «ця — специфічна для сесії».
5. Latency та offline
// main.tsx, Lines 1-8 // These side-effects must run before all other imports: // 1. profileCheckpoint marks entry before heavy module evaluation begins // 2. startMdmRawRead fires MDM subprocesses so they run in parallel // with the remaining ~135ms of imports below
Додатковий network round-trip для отримання prompt додав би 50-300ms до кожного запуску. А якщо мережі немає — CLI взагалі не зможе стартувати (або потрібен fallback на вбудовані промпти, що нівелює весь підхід).
Де серверний підхід мав би сенс
Серверний prompt registry підходить для інших сценаріїв:
| Сценарій | Чому серверні промпти працюють |
|---|---|
| SaaS chatbot | Один сервер, немає client-side context, hot-fix без deploy |
| RAG pipeline | Prompt templates мало залежать від клієнта |
| Multi-tenant platform | Різні промпти для різних tenant’ів, централізоване управління |
| A/B testing | Потрібно швидко міняти prompt для % користувачів |
| Regulated industries | Аудит, versioning, compliance вимагають централізації |
Claude Code — жодний з цих сценаріїв. Це thick client (CLI на машині розробника), де prompt є функцією локального стану.
Що проєкт вже робить правильно (гібридний підхід)
Проєкт насправді вже має елементи серверного управління промптами, просто не через «prompt registry»:
GrowthBook feature flags — серверне управління тим, які секції prompt включати:
// prompts.ts, Lines 391-394
feature('VERIFICATION_AGENT') &&
getFeatureValue_CACHED_MAY_BE_STALE('tengu_hive_evidence', false)
? `The contract: when non-trivial implementation happens...`
: null,
Remote model override section — server-driven suffix для system prompt:
// prompts.ts, Lines 136-140
function getAntModelOverrideSection(): string | null {
if (process.env.USER_TYPE !== 'ant') return null
if (isUndercover()) return null
return getAntModelOverrideConfig()?.defaultSystemPromptSuffix || null
}
MCP server instructions — зовнішні сервери надають свої інструкції для prompt:
// prompts.ts, Lines 579-603
function getMcpInstructions(mcpClients: MCPServerConnection[]): string | null {
const connectedClients = mcpClients.filter(
(client): client is ConnectedMCPServer => client.type === 'connected',
)
const clientsWithInstructions = connectedClients.filter(
client => client.instructions,
)
// ...
return `# MCP Server Instructions\n\n${instructionBlocks}`
}
Build-time DCE — різні prompt тексти для ant vs external через feature():
// prompts.ts, Lines 466-489
if (
(feature('PROACTIVE') || feature('KAIROS')) &&
proactiveModule?.isProactiveActive()
) {
return [
`\nYou are an autonomous agent...`,
// completely different prompt path
]
}
Це — гібридна модель: структура prompt визначена в коді (де вона тісно пов’язана з логікою), але параметри поведінки (feature gates, remote settings) — серверні.
Що можна покращити (не переходячи на server-side prompts)
Якщо мета — зменшити coupling і покращити managability, є кращі підходи:
1. Prompt Composition DSL
Замість inline strings — декларативна структура:
// Замість поточного:
function getSimpleDoingTasksSection(): string {
const items = [
`The user will primarily request...`,
...(process.env.USER_TYPE === 'ant'
? [`Default to writing no comments...`]
: []),
]
return [`# Doing tasks`, ...prependBullets(items)].join('\n')
}
// Можна:
const doingTasksSection = defineSection('doing_tasks', {
title: 'Doing tasks',
items: [
item('The user will primarily request...'),
item('Default to writing no comments...', { variant: 'ant' }),
item('Before reporting a task complete...', { variant: 'ant', tag: 'MODEL_LAUNCH' }),
],
})
2. Prompt versioning з migration
Замість @[MODEL LAUNCH] коментарів — explicit version migration:
const PROMPT_VERSION = 'v46.2'
const modelConfig = {
'v46.2': {
frontier: 'Claude Opus 4.6',
cutoffs: { 'claude-opus-4-6': 'May 2025' },
},
'v45.1': {
frontier: 'Claude Sonnet 4.5',
cutoffs: { ... },
},
}
3. Structured prompt diff для A/B testing
Замість inline getFeatureValue_CACHED_MAY_BE_STALE() — centralized variant registry з diff-based telemetry.
Висновок
| Критерій | Серверні промпти | Поточний підхід (клієнтські) |
|---|---|---|
| Tight coupling з runtime state | Потребує серіалізації всього контексту | Природний доступ |
| Приватність CLAUDE.md | Проблематично | Дані не покидають машину |
| Latency | +50-300ms per startup | 0ms |
| Offline | Неможливо без fallback | Повна підтримка |
| Version skew (tool ↔ prompt) | Ризик розсинхронізації | Завжди синхронізовані |
| Hot-fix промптів | Так, без нового релізу | Потребує нового релізу |
| Prompt cache optimization | Складніше контролювати | Повний контроль (boundary marker) |
| Centralized audit | Природній | Через git history |
Для цього проєкту поточний гібридний підхід (клієнтські промпти + серверні feature flags) є оптимальним. Промпти — це не «контент», який можна винести у CMS. Вони — невід’ємна частина поведінки клієнта, тісно зв’язана з tool definitions, runtime state, файловою системою та permissions моделлю. Виносити їх на сервер — це як виносити if/else у мікросервіс: формально можливо, але створює більше проблем, ніж вирішує.
Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.
32 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів