Археологія Claude Code: як виглядає код з майбутнього

💡 Усі статті, обговорення, новини про AI — в одному місці. Приєднуйтесь до AI спільноти!

Сьогодні трапився цікавий інцидент — у 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 рядків (884–4513). Це де-факто god module: CLI-визначення, init-пайплайн, feature-flag branching, телеметрія, міграції, deferred prefetches — все в одному файлі. Хоча це не класичний God Object (немає гігантського класу), обсяг відповідальності надмірний.

// 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.ts5,512Невиправдано: фабрики повідомлень можна розбити по типах
utils/sessionStorage.ts5,105Невиправдано: змішує persistence + querying + migration
utils/hooks.ts5,022Невиправдано: «catch-all» для git/shell хуків
screens/REPL.tsx5,006Критично: God Component з 68 useState, ~80 імпортами
main.tsx4,684Невиправдано: god module (розбирався вище)
utils/plugins/pluginLoader.ts3,302Потребує декомпозиції
utils/plugins/marketplaceManager.ts2,643На межі допустимого
tools/BashTool/bashSecurity.ts2,593Частково виправдано складністю домену безпеки
utils/shell/readOnlyCommandValidation.ts1,893Великі lookup-таблиці — можна виділити
utils/powershell/parser.ts1,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 — кілька TODO
  • entrypoints/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 (більшість у коментарях або генерованому коді), ~10 as 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 з __brand
  • utils/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
  • Низький: Зменшити кількість as assertions — деякі можна замінити 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: 2
  • utils/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, =cmd expansion
  • 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, emailFetchPromise
  • utils/config.ts — globalConfigCache
  • utils/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 з 68 useState — кожен setState може викликати re-render
  • commands/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 — 68 useState можуть спричиняти 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-scope let) ускладнюють ізоляцію
  • process.env читається безпосередньо — mock вимагає глобальної підміни

Найскладніше тестувати:

  • REPL.tsx — 68 internal states, 80+ imports, 4,430 lines
  • main.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. Технічний борг

Реєстр технічного боргу:

#ПріоритетОписДеРизикСкладність
1CriticalGod Component REPL.tsx — 5,006 lines, 68 useStatescreens/REPL.tsxБлокує розвиток UI, maintainability near zeroВисока (тижні)
2CriticalGod Module main.tsx:run() — 3,600 linesmain.tsx:884-4513Будь-яка зміна CLI потребує навігації по 3,600 рядкахВисока
3High57+ .catch(() => {}) — проковтування помилок38+ файлівНевидимі збої в productionСередня
4High82+ «external» === ’ant’ без абстракції27+ файлівShotgun Surgery при зміні build variant logicНизька
5Highutils/ як catch-all (564 файли)utils/**Навігація O(n), розмиті межі модулівВисока
6Medium88 TODO коментарів без трекінгуРізні файлиЗабуті задачіНизька
7MediumRace conditions у inflight promise cachesuser.ts, fastMode.ts, ParsedCommand.tsРідкісні баги при concurrent accessСередня
8MediumДублювання import ./bootstrap/state.jsmain.tsx:87,168Confusion, minorМінімальна
9LowuseReducer лише у 3 файлах, 6 usageВесь проєктuseState sprawl у складних компонентахСередня
10LowОбмежений DI (лише QueryDeps)query/deps.tsУскладнене тестуванняВисока

Борг, що блокує розвиток: #1, #2, #5

Борг, з яким можна жити: #6, #8, #9

10. Паттерни проєктування

Загальна оцінка: 7/10

Використані паттерни

ПаттернДеОцінка
Composition Rootmain.tsxДоречно, але overloaded
Observer/Signalutils/signal.tsЕлегантно, замінює 15× дублювання
External Storestate/store.ts + useSyncExternalStoreСучасний, мінімалістичний
Feature Flag / DCEbun:bundle feature()Effective build-time elimination
SingletonDiagnosticTrackingService, ActivityManager, AwsAuthStatusManagerСтандартно
StrategyPermission rules pipelineДобре розділяє правила та execution
Plugin Systemplugins/, skills/, commands/Extensible architecture
Circuit BreakerAuto mode, autocompactProduction-grade resilience
PKCE OAuthservices/oauth/Industry standard
DI (prototype)query/deps.tsПравильний напрямок, обмежений scope

Антипатерни

АнтипатернПрикладSeverity
God Object/ModuleREPL.tsx, main.tsx:run()Critical
Shotgun Surgery"external" === 'ant' у 27+ файлахHigh
Feature Envymain.tsx знає деталі teleport, plugins, MCP, SSH, etc.High
Primitive ObsessionModule-level let замість encapsulated state objectsMedium
Swiss Army Knifeutils/ з 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

Найбільші технічні ризики

  1. REPL.tsx як single point of failure — будь-який баг у 5,006-рядковому компоненті ламає весь UI. Refactoring ризикований через coupling між 68 state variables.
  2. Module-level mutable state — сотні let змінних без формальної ownership моделі. При масштабуванні (multi-agent, concurrent sessions) race conditions стануть частішими.
  3. Bun runtime lock-in — import { feature } from 'bun:bundle', bun:bundle side effects, React Compiler runtime — все це прив’язує до Bun. Міграція на Node.js або Deno потребуватиме значних зусиль.
  4. bashSecurity.ts surface area — 2,593 рядки security-critical коду для блокування shell injection. Кожна нова shell, нова версія bash/zsh може ввести bypass. Потребує continuous fuzzing.
  5. Bus factor — main.tsx:run() (3,600 lines), REPL.tsx (5,006 lines), bashSecurity.ts (2,593 lines) — ці файли настільки великі, що лише автори повністю розуміють всі flow branches.
  6. Зовнішні залежності:
    • @anthropic-ai/sdk — зміни API можуть зламати services/api/claude.ts
    • react-reconciler — кастомний Ink renderer залежить від internal React API
    • Зміни у Commander.js TypeScript typings (вже є workaround з Object.assign)

Зведена таблиця оцінок

РозділОцінкаКоментар
1. Архітектура6/10God modules, розмита utils/, але продумана plugin system
2. Якість коду5/10Файли-гіганти, God Component REPL.tsx, але хороші коментарі
3. TypeScript8/10Майже 0 any, Zod validation, branded types
4. Обробка помилок7/10Circuit breakers, retry, але 57+ swallowed errors
5. Безпека9/10Enterprise-grade: env scrubbing, bash sanitization, PKCE
6. Стан6/10Хороший store pattern, але 68 useState, race conditions
7. Продуктивність7/10Startup optimization, React Compiler, lazy loading
8. Тестованість5/10Module singletons, обмежений DI, god components
9. Технічний борг5/103 critical items, 88 TODOs, shotgun surgery pattern
10. Паттерни7/10Signal, Store, Circuit Breaker — але God Object антипатерн
11. Сильні сторони8/10Security, startup perf, signal primitive, Zod
12. Ризики6/10Bun 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 найкритичніших проблем

  1. God Component REPL.tsx (5,006 lines, 68 useState) — потребує декомпозиції на 10-15 під-компонентів з custom hooks. Блокує розвиток UI.
  2. God Module main.tsx:run() (3,600 lines) — потребує розбиття на CLI definition, init pipeline, feature handlers. Блокує розвиток CLI.
  3. 57+ проковтаних помилок (.catch(() => {})) — потенційно приховує production-баги, особливо у bridge/ та MCP client.
  4. Shotgun Surgery: «external» === ’ant’ (82+ використань у 27+ файлах) — кожна зміна build variant логіки потребує правок у десятках файлів.
  5. utils/ як catch-all (564 файли) — розмиває межі відповідальності модулів, ускладнює навігацію та рефакторинг.

Топ-5 найсильніших рішень

  1. 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 інструменту.
  2. createSignal<Args>() primitive — 44 рядки коду, що замінили 15× дублювання pub/sub boilerplate. Типізований, мінімалістичний, з subscribe/emit/clear API.
  3. createStore<T>() + useSyncExternalStore — 35 рядків мінімального external store замість важких state management бібліотек. Object.is equality, functional updates, listener cleanup — все необхідне без зайвого.
  4. Startup performance optimization — паралельні subprocess launches (MDM, keychain) під час import evaluation; profileCheckpoint для трекінгу; preAction hook для відкладеної ініціалізації; React Compiler для auto-memoization.
  5. 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: "..." }

Обидва варіанти мають фундаментальні проблеми для CLI-інструменту. Розглянемо їх.

Чому серверний підхід не підходить саме тут

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

CLI-інструмент повинен запускатися швидко. Поточна startup optimization — паралельні subprocess launches під час import evaluation:

// 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 pipelinePrompt 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 startup0ms
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 у мікросервіс: формально можливо, але створює більше проблем, ніж вирішує.

Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.

👍ПодобаєтьсяСподобалось23
До обраногоВ обраному6
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Дивно що Trump досі не прокоментував

будує агента, що прочитає тест і видасть йому коментар

питання вже не в тому, «чи ідеальний це код».

а питання в тому чи цей код містить баги, і діри безпеки. І сама наявність цієї статті дає відповідь на це питання.

А реальне питання, чи ваш (не конкретно ваш, а читача в загалом, не містить багів?)

сотни миллиардов сегодня «стоит» математика, которая стоит за моделью и ее обучение.
нагенеренный гавнокод в данном контексте мало чего стоит.
и прям «вау» тот факт, что это все было нагенерировано сегодня точно не вызывает. нагенерить можно много чего, вопрос, сможешь ли ты при этом заложить хорошую архитектуру и держать под контролем технических долг.

На публікації промпту вже можна було завершити «статтю». Кому цікаво — зробили все інше самі.

Код АІ агента прожарили АІ агентом та виклали цей потужний АІ аналіз

Ну таке собі

Цей коментар я чекав, дивно що не прилетів в перші хвилини. Пропоную тепер написати в чому AI-агент не правий, буде дуже цікаво почитати, на повному серйозі, без підколів.

А чому він правий? Тут питання чи треба на це витрачати час взашалі? Кожен може запустити аналіз, якщо захоче. Промптами можна налаштувати очікуємий результат. Тільки навіщо?

Проблема в тому, що автор статті згенерував value за допомогою АІ, а автор коментаря — зміг згенерувати АІ слоб коментар без АІ, чистими руками :)

Я прочитатв статтю з хай левельним розбором, зекономив свій час на пошук сорс кода(його підчистили з оргініальної репи і нпм), зекономив час на думки про «а не бекдор це я скачав», зекономив 5-10 хвилин на запуск агентів і зекономив токени.
Мені ок прочитати з доу, а не з md файла згенерованого власноруч — а якщо буде потрібно, вже піду і копну глибше сам.

Якщо для мене це value не потрібно, чи я хочу сам поколупатися — я пройду повз, це цілком нормально.
А коментити статті, які мені не зайшли «ну це таке собі» ніякої користі не несе взагалі

У нас просто різні погляди на value.

Для мене value це про власний досвід, це про експертизу, це про research, витрачений час на аналіз, тобто думки людини через призму своєї особистості.

Якщо людина відкриває якийсь LLM інтерфейс і пише промпт «згенери мені статтю» то це не value.

Бо нічого з вище сказаного не було задіяно. Це просте делегування мозкової активності до LLM.

Інша справа якби LLM було використане як інструмент, де особистий досвід перетинався з використанням LLM для первинного аналізу, щоб його доопрацювати і так далі.

Тому ви надто чутливо сприйняли коментар, якщо ви не згодні з моїм коментарем ви також могли б пройти мимо, але не пройшли. Знаєте чому?

Бо обмін думками це нормально, бо в дискусії знаходиться істина.

P.S. Не зайшов мені саме формат статті, а не сама стаття. Це трохи різне, але звісно схоже. А будь який коментар, піднімає пост для читачів, тому тут нічного поганого бути не може.

Ну... як раз особисто для себе я не бачу value, мій пост про це. Досить рандомна інформація, написана в нечитаємому притаманному LLM занадно структурованому стилі, з яким й так воєш в повсякденному житті, бо для мене неможливо з цим працювати. Це типове надування щік, спроба показати вел’ю, де його немає взагалі.

згенерував value

Чи пробували ви взяти це «value», і завантажити іншій ллмці (або й цій самій) ллмці разом з кодом з промптом в стилі «дай фідбек на фідбек. Ось код, ось фідбек»? А тоді той фідбек послати в попередній чат? Спробуйте.

Я коли роблю LLM-код рев’ю свого коду, то регулярно так роблю. І зазвичай виходить наступна ситуація:
Чат1 (зроби фідбек): ну тут є помилки а, b, c, d.
Чат 2 (зроби фідбек на фідбек): ну стосовно b він правий частково, стосовно c правий, а от a і d — це взагалі мимо.
Чат1 (як закинув йому фідбек на фідбек): ой справді, вибачте, тут є неточність. А ще я ось тут помилився, але ще ось тут знайшов дещо.

А коли починаю сам іти, так виявляється, що навіть те, з чим обидві llmки погодились, лише половина вірне, в ще половина взагалі галюцинація.

Потім кидаю PR на рев’ю колегам, і вони можуть знайти щось, про що llmки взагалі не згадали.

Веду я до того, що справжнього value в такому «value» — лише частинка. Причому кожен раз різна частинка. В чомусь вгадує, в чомусь ні.

У випадку з LLM рев’ю власного коду це всеодно має сенс. Бо там ти можеш адекватно оцінити той фідбек і llmка може звернути увагу на щось, про що не подумав.

Але отак всліпу чужий закинути код llmці, а тоді всерйоз з повною довірою читати той фідбек — це свою внутрішню нейронку в себе в голові на шкідливому вчити.

Якщощо, я не хейтер LLMок. Я їх теж використовую. Там, де вони корисні і так, як вони корисні.
І трігернуло мене не те, що люди посміли використати LLM. Мене трігернуло, що одна людина скопіпастила відповідь від LLM навіть нормально не валідувавши ту відповідь (та ще й подавши це як статтю) А інша людина це називає value так, ніби справді там 100% value (хоча насправді value там завжди рандомна кількість).

Тобто ви не витратили ніяких зусиль, не вклали особистого досвіду чи експертизи, а просто скопіювали AI генерацію сюди. І це типу стаття.

І тепер хочете, щоб я за вас витратив свій час, використав свою експертизу та знання?)

На щастя, в інтернеті вже купа авторських та дійсно цікавих розборів. Рекомендую вам з ними ознайомитися. І тоді у вас відпадуть питання, щодо якості конкретно вашої AI генерації.

AI не розуміє, що може бути цікавим людині, бо це статистичний генератор текста без контексту. Ось це сама головна проблема таких аналізів.

Крім того оцінка від AI в балах це також мимо, не вміє воно оцінювати взагалі.

Я ще спостерігаю дивну ситуацію, авторитетні люди в індустрії (принаймі з багаторічним досвідом), заходять в тему AI і автоматично одразу стають AI експертами. Здавалося їх досвід мав би їх приземлити, як це трапляється в науковців, але часто виходить навпаки.

Коли вказуєш на поспішні висновки без достатньо практики чи дослідження, відповідь нерідко з’їжджають в персоналії замість розбору по суті. Можливо, практики справді достатньо, але якщо вона не показана, то зовні це виглядає не як перевірений висновок, а як пропозиція просто повірити на слово авторитету.

Нормально, що людина не може охопити все. Не ок — коли інших повчають зверхньо, але не показують, на чому саме тримається висновок і як до нього дійшли

Дуже альтернативна точку зору. Шахісти від того, що оцінюють силу гри комп’ютерних програм не стають програмістами. Так само розробники, які оцінюють як AI будує програмне забезпечення оцінюють саме створення програмного забезпечення. Використнання LLM також не супер-пупер технологія, потребує лише простого володіння рідною мовою. Як раз ці відповіді досить практичні: спробував, отримав недостатньо якісний код. Тільки ви це чомусь називаєте «персоналіями», насправді це особистий досвід.

І Claude Code це просто приклад софту, який пише фірма з майже необмеженими можливостями в плані як найму так і в плані обчислювальних потужностей, і... мене, чесно, не вражає ні якість, ні швидкість розробки. Той же neovim невелика команда розробила за два роки, і це виглядає як більш складний проєкт, як на мене.

Ваш цей заклик «ну да я зробив ctrl c ctrl v з llmки, і що? так не нравиця іди зраби нармальна».
З таким підходом можна далеко зайти: «Ну да я переклеїв строк придатності на кефірові. Не нравиця — так відкрий магазин продавай тільки свіже». «Ну да я убив людину? Не нравиця — ну так не вбивай людей». Типу...

Негативні коментарі цілком справедливі і очікувані.

Люди почуваються ошуканими.

Вони думали знайшовся доброволець, та ще й достатньо експертний, який витратив кілька діб свого життя, сам заліз в той код і готовий поділитись інсайтами. Ручки чешуться цікаво почитати що ж там що ж там. Відкриваєш, а там тіп просто скопіював шібурду llmки...

Якщо генерацію і аналіз роблять різні клієнти, то це доречно.

Але на статтю це аж ніяк не тягне)

А першого квітня з’ясується, що вони пожартували і виклали свідомо неробочий код, щоб перевірити чи зможуть ентузіасти його поліпшити.

Якщо хтось на повному серйозі буде так само оцінювати свій або командний код, то використовуйте хоча б GPT-5.4 High, бо слабша модель, типу Opus 4.6 буде більш оптимістична. Звісно, це ще залежить від інструменту, який ви використовуєте: які шматки коду потрапили в контекст, які додаткові дані про вас модель знає тощо — усе це впливає на якість оцінки. В іншому випадку ви просто отримаєте «те, що хочете почути»

Чому ви вважаєте що GPT-5.4 High краще нвж Opus 4.6?
Є десь бенчмарки, порівняння?
Просто цікаво це суб’єктивне враження чи ні.
Дякую.

Я написав, що Opus 4.6 слабша для такого типу завдань. У моєму коментарі немає поняття «краща». Я рекомендую GPT-5.4 (з високим reasoning), з розумінням, що людина буде використовувати Codex (app чи CLI), тобто інструмент від виробника моделі.

Щодо бенчмарків, то ви й самі можете подивитися актуальні. Для такого типу завдань я б дивився на SWE Atlas Codebase QnA. А якщо брати загальні бенчмарки, то ARC-AGI-2 чи LiveBench.
Проте для мене бенчмарк — це просто показник, який мало про що говорить. Деякі бенчмарки втратили актуальність, бо вони статичні — не враховують нові моделі та орієнтовані на вузькі задачі. Крім того, задачі потрапляють у навчальні дані моделі, і, звісно, такий бенчмарк стає малокорисним.

Коли в мене виникає складність у вирішенні тех. питання, я роблю кілька тестів: закидаю однакове завдання в API моделей (чистий контекст) і порівнюю. Також порівнюю те саме в їхніх основних інструментах (Claude Code, Codex, Gemini CLI/Antigravity), і тоді вже роблю висновок чи це працює для такого типу завдань.
Особисто я використовую щонайменше ці 3 моделі в різних контекстах кожного дня, щоб мінімізувати заангажованість «одного бачення»

Хоч я сам загалом люблю GPT 5.4 High, але він з цією задачею справився гірше за Opus. Загалом це приблизно одного рівня моделі, я би не поспішав висловлюватися так категорично в термінах слабша/сильніша, бо маю звичку різні моделі та інструменти порівнювати практично постійно, і можу підтвердити що в більшості моїх задач Opus на першому місці, а GPT 5.4 — на другому, хоча нерідко буває і навпаки.

Порівняння результату різних моделей я не додадав в статтю, щоб не ускладнювати, але я моделі зазвичай запускаю в паралель, у Cursor це зручно, і цього разу теж так робив :)

Я запустив свій улюблений набір для складних задач: Opus 4.6 High, Composer 2, Gemini 3.1 Pro та GPT 5.4 High. Проглянув їхню видачу, найбільш детальний результат з гарними коментарями видав саме Opus, тому його й закинув в статтю.

Видача GPT 5.4 не сподобалася з першого речення воно почало писати: «Це радше layered monolith з registry/plugin-елементами, а не hexagonal architecture», але чому про hexagonal згадало таким чином? Ніхто ж йому не говорив що проєкт по ній зроблений, задача була лише ідентифікувати. Також мені не сподобалося ні те як 5 проблем воно сформувало (у якості проблеми відзначило відсутність тестів, але це й так зрозуміло що це частина проєктів без тексту). І загалом результат вийшов дуже сухий без пояснень, нецікаво читати взагалі, якщо комусь цікаво, можу окремо видачі вигрузити для порівняння, самі почитаєте.

Хоча загалом результат тієї моделі теж норм, як і у інших. Просто я не ставив задачу прям детально дослідити і відкопати кожен нюанс.

Якщо задачу так ставити, то її треба робити з іншою методикою. Тоді краще видачу всіх моделей вигрузити, і провести «узагальнюючий» раунд над всіма результатами. І так можливо зробити кілька разів (бо видача моделей недермінована) ідентифікуючи слабкі місця, а потім валідуючи їх наявність ще додатковими прогонами.

І звичайно що від інструмента багато що залежить, і в цьому сенсу Cursor прекрасний інструмент, який дуже добре вміє в context engineering, чистоту експерименту дає значно кращу, ніж інструменти від вендорів моделей.

Мабуть подібні порівняння, як регулярний інструмент, потребують чималий бюджет?

Я недавно знімав відосик про Cursor і в нього є маловідома особливість — коли ти платиш за підписку $200 на місяць, то вона включає $400 API викликів, які я використовую для Premium моделей, а тарифікація за моделі в Auto+Composer йде окремо.
І мені цих грошей вистачає прям на дуже активну роботу, хоча звісно не нон-стоп преміум, але дуже часто в паралель кілька агентів запускаю, якщо розумію що задача відповідальна і один може не справитися, або цікаво буде результати порівняти.
Для задач простіших обираю режим Auto чи Composer, остання версія Composer 2 прям дуже радує, більшість рутинних задач виконує без зауважень. Premium моделі треба лише для особливо складних тасок.
Відосик тут

короче, то він згенернований ШІ чи ні? :)

Самі читайти такі «статті»

Підписатись на коментарі