{
	"version": "https://jsonfeed.org/version/1",
	"title": "Eugene Burachevskiy",
	"icon": "https://avatars.micro.blog/avatars/2026/02/1858407.jpg",
	"home_page_url": "https://ai-vs-devops.micro.blog/",
	"feed_url": "https://ai-vs-devops.micro.blog/feed.json",
	"items": [
			{
				"id": "http://ai-vs-devops.micro.blog/2026/03/03/claude-code-opencode-telegram.html",
				"title": "Нотификации из Claude Code и OpenCode в ваш Telegram",
				"content_html": "<p>Зачастую в процессе работы с LLM приходится долго ждать ответа. Или модели медленные, или задача сильно большая, или агент очень активно траблшутит проблему и настойчиво пытается ее решить. Идешь пить чай, каждые пару минут возвращаешься за ноут, а работа все еще идет. Удобно было бы получать нотификации, например в телегу, которая всегда под рукой. И реализуется это все с помощью хуков. Недавно сделал себе такое, и в целом доволен, мелочь, а приятно :) Плюс когда даешь задачи на ресерч или брейншторминг, с телефона почитать даже удобнее. Решил поделиться рецептом с вами.</p>\n<p>В Claude Code и Курсоре все просто, хуки бывают двух типов, command и prompt ( на определенное событие запустить шел команду или отправить промпт модели), описываются в hooks.json. Сообщение через бота можно послать просто используя curl. Но мне изначально было интересно сделать нотификацию из OpenCode, и там уже все чуть сложнее. Опенкод не поддерживает обычные хуки, но поддерживает typescript плагины, кастомный код который подгружается вместе с клиентом и может обрабатывать события и по ним запускать нужную логику.</p>\n<p>Но начнем мы с простого бота.</p>\n<p>Идем в телеграм, ищем поиском BotFather, отправляем команду /newbot и создаем нашего нового бота. После создания нам дадут токен в формате &ldquo;айдибота:токен&rdquo;, сохраняем это себе.</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/screenshot-from-2026-03-03-19-15-11.png\" width=\"600\" height=\"505\" alt=\"\">\n<p>Дальше или идем в личку боту и просто что нибудь ему пишем. Или создаем новую группу и добавляем в нее нашего бота (кликаем на бота в контактах, дальше more &gt; add to group). Я выбрал второй вариант, так как в планах использовать несколько ботов (каждый привязан к разным ide или к разных машинам) и читать обновления в одной группе.</p>\n<p>Дальше запускаем curl и получаем апдейты с бота,</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-bash\" data-lang=\"bash\">curl https://api.telegram.org/bot&lt;BOT_TOKEN&gt;/getUpdates\n</code></pre></div><p>в ответе видим список сообщений который получал бот, там chatId нашего приватного или группового чата, сохраняем.</p>\n<p>Дальше в случае Claude Code все просто, добавляем json с хуками в .claude/settings.json</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-json\" data-lang=\"json\">{\n  <span style=\"color:#f92672\">&#34;hooks&#34;</span>: {\n    <span style=\"color:#f92672\">&#34;Stop&#34;</span>: [\n      {\n        <span style=\"color:#f92672\">&#34;hooks&#34;</span>: [\n          {\n            <span style=\"color:#f92672\">&#34;type&#34;</span>: <span style=\"color:#e6db74\">&#34;agent&#34;</span>,\n            <span style=\"color:#f92672\">&#34;prompt&#34;</span>: <span style=\"color:#e6db74\">&#34;Check the last result of the session, remember it as a &lt;LAST_MESSAGE&gt; (make sure it is not more then 4000 symbols). And then run in the shell: curl -s -X POST https://api.telegram.org/bot&lt;TOKEN&gt;/sendMessage -H &#34;</span><span style=\"color:#960050;background-color:#1e0010\">Content-Type</span>: <span style=\"color:#960050;background-color:#1e0010\">application/json</span><span style=\"color:#e6db74\">&#34; -d &#39;{&#34;</span><span style=\"color:#960050;background-color:#1e0010\">chat_id</span><span style=\"color:#e6db74\">&#34;: &#34;</span><span style=\"color:#960050;background-color:#1e0010\">&lt;CHAT_ID&gt;</span><span style=\"color:#e6db74\">&#34;, &#34;</span><span style=\"color:#960050;background-color:#1e0010\">text</span><span style=\"color:#e6db74\">&#34;: &#34;</span><span style=\"color:#960050;background-color:#1e0010\">&lt;LAST_MESSAGE&gt;</span><span style=\"color:#e6db74\">&#34;}&#39; &#34;</span>\n          }\n        ]\n      }\n    ],\n    <span style=\"color:#f92672\">&#34;PermissionRequest&#34;</span>: [\n      {\n        <span style=\"color:#f92672\">&#34;hooks&#34;</span>: [\n          {\n            <span style=\"color:#f92672\">&#34;type&#34;</span>: <span style=\"color:#e6db74\">&#34;command&#34;</span>,\n            <span style=\"color:#f92672\">&#34;command&#34;</span>: <span style=\"color:#e6db74\">&#34;curl -s -X POST https://api.telegram.org/bot&lt;TOKEN&gt;/sendMessage -H &#34;</span><span style=\"color:#960050;background-color:#1e0010\">Content-Type</span>: <span style=\"color:#960050;background-color:#1e0010\">application/json</span><span style=\"color:#e6db74\">&#34; -d &#39;{&#34;</span><span style=\"color:#960050;background-color:#1e0010\">chat_id</span><span style=\"color:#e6db74\">&#34;: &#34;</span><span style=\"color:#960050;background-color:#1e0010\">&lt;CHAT_ID&gt;</span><span style=\"color:#e6db74\">&#34;, &#34;</span><span style=\"color:#960050;background-color:#1e0010\">text</span><span style=\"color:#e6db74\">&#34;: &#34;</span><span style=\"color:#960050;background-color:#1e0010\">⚠️Permission</span> <span style=\"color:#960050;background-color:#1e0010\">requeired!</span><span style=\"color:#e6db74\">&#34;}&#39;&#34;</span>\n          }          \n        ]\n      }\n    ]\n  }\n}\n</code></pre></div><p>В Cursor все аналогично, можете прочитать про хуки и глянуть примеры в <a href=\"https://cursor.com/docs/agent/hooks\">https://cursor.com/docs/agent/hooks</a>\nПомещаем аналогичный json с событием&gt;хуком в .cursor/hooks.json, если нужно что-то посложнее, например обработку транскрипт файлов, то пишем баш скрипт, кладем в .cursor/hooks/ директорию и вызываем скрипт через command хук.</p>\n<p>В OpenCode же пошли своим путем. На анонсе писали, что добавят что-то покруче&hellip;</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/screenshot-from-2026-03-03-21-15-50.png\" width=\"600\" height=\"250\" alt=\"\">\n<p>Но оказалось, что в реализации все не так просто. Вобщем спорно, &ldquo;круче&rdquo; - это когда гениально просто, или когда это сложный навороченый конструктор для гиков?))</p>\n<p>Вобщем надо писать плагины на js/typescript, которые будут обрабатывать события и ходить в API. Кстати только узнал, что в opencode клиент серверная архитектура, твой TUI - это клиент, а еще опенкод поднимает локальный апи сервер, через который TUI или cli, или любой другой клиент может управлять чатами и сессиями.</p>\n<p>Итого пришлось повайбкодить, а еще почитать документацию по плагинам и ивентам:\n<a href=\"https://opencode.ai/docs/plugins/\">https://opencode.ai/docs/plugins/</a>\nи документацию по апи опенкода:\n<a href=\"https://opencode.ai/docs/server/#apis\">https://opencode.ai/docs/server/#apis</a></p>\n<p>Потому что свайбкодить все за один присест не получилось, модель писала какую-то нерабочую фигню, пришлось подсказывать ей какие апи использовать, какие респонсы мы ожидаем итд. :D</p>\n<p>В итоге получился такой код. Сохраняем тг токен и айди чата в ~/.bashrc в переменных OP_TELEGRAM_TOKEN и OP_TELEGRAM_CHAT_ID, чтобы хранить их секьюрно.</p>\n<p>Скрипт работает в фоне и следит за ивентами, на ивенте session.idle (когда аи вам ответила в чате и ждет новых команд) забираем номер сессии, по номеру сессии забераем последний мессейдж агента. По факту там лежит все, и рассуждения, и вызовы тулов, разбираем этот лист и достаем только финальный реплай. Если размер больше 4к символов - транкейтим, чтобы влезло в сообщение телеграм (с этим есть какой-то нюанс, мне кажется с транкейтом не совсем корреткно работало, надо потестить больше :)). И дальше отсылаем в тг апи через обычный fetch.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-typescript\" data-lang=\"typescript\"><span style=\"color:#75715e\">/**\n</span><span style=\"color:#75715e\"> * TelegramNotifyPlugin for OpenCode\n</span><span style=\"color:#75715e\"> * \n</span><span style=\"color:#75715e\"> * Sends Telegram notifications with the final agent response when sessions complete.\n</span><span style=\"color:#75715e\"> * Uses OpenCode API: GET /session/:id/message/:messageID\n</span><span style=\"color:#75715e\"> */</span>\n\n<span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">MIN_TEXT_LENGTH</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#ae81ff\">10</span>;\n<span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">TELEGRAM_MAX_LENGTH</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#ae81ff\">4096</span>;\n<span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">TELEGRAM_TIMEOUT_MS</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#ae81ff\">5000</span>;\n\n<span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">pendingOperations</span>: <span style=\"color:#66d9ef\">Promise</span>&lt;<span style=\"color:#f92672\">void</span>&gt;[] <span style=\"color:#f92672\">=</span> [];\n\n<span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">addPendingOperation</span>(<span style=\"color:#a6e22e\">promise</span>: <span style=\"color:#66d9ef\">Promise</span>&lt;<span style=\"color:#f92672\">void</span>&gt;)<span style=\"color:#f92672\">:</span> <span style=\"color:#66d9ef\">void</span> {\n  <span style=\"color:#a6e22e\">pendingOperations</span>.<span style=\"color:#a6e22e\">push</span>(<span style=\"color:#a6e22e\">promise</span>);\n  <span style=\"color:#a6e22e\">promise</span>.<span style=\"color:#a6e22e\">then</span>(() <span style=\"color:#f92672\">=&gt;</span> {\n    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">idx</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">pendingOperations</span>.<span style=\"color:#a6e22e\">indexOf</span>(<span style=\"color:#a6e22e\">promise</span>);\n    <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">idx</span> <span style=\"color:#f92672\">&gt;</span> <span style=\"color:#f92672\">-</span><span style=\"color:#ae81ff\">1</span>) <span style=\"color:#a6e22e\">pendingOperations</span>.<span style=\"color:#a6e22e\">splice</span>(<span style=\"color:#a6e22e\">idx</span>, <span style=\"color:#ae81ff\">1</span>);\n  });\n}\n\n<span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">on</span>(<span style=\"color:#e6db74\">&#39;beforeExit&#39;</span>, <span style=\"color:#66d9ef\">async</span> () <span style=\"color:#f92672\">=&gt;</span> {\n  <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">pendingOperations</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">&gt;</span> <span style=\"color:#ae81ff\">0</span>) {\n    <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">Promise</span>.<span style=\"color:#a6e22e\">all</span>(<span style=\"color:#a6e22e\">pendingOperations</span>);\n  }\n});\n\n<span style=\"color:#66d9ef\">interface</span> <span style=\"color:#a6e22e\">OpenCodeClient</span> {\n  <span style=\"color:#a6e22e\">_client</span><span style=\"color:#f92672\">:</span> {\n    <span style=\"color:#a6e22e\">request</span><span style=\"color:#f92672\">:</span> (<span style=\"color:#a6e22e\">config</span><span style=\"color:#f92672\">:</span> { <span style=\"color:#a6e22e\">method</span>: <span style=\"color:#66d9ef\">string</span>; <span style=\"color:#a6e22e\">url</span>: <span style=\"color:#66d9ef\">string</span> }) <span style=\"color:#f92672\">=&gt;</span> <span style=\"color:#a6e22e\">Promise</span><span style=\"color:#f92672\">&lt;</span>{ <span style=\"color:#a6e22e\">data</span>: <span style=\"color:#66d9ef\">MessageListResponse</span> }<span style=\"color:#f92672\">&gt;</span>;\n  };\n}\n\n<span style=\"color:#66d9ef\">interface</span> <span style=\"color:#a6e22e\">MessageListResponse</span> {\n  <span style=\"color:#a6e22e\">info</span><span style=\"color:#f92672\">?:</span> { <span style=\"color:#a6e22e\">id</span>: <span style=\"color:#66d9ef\">string</span> };\n}\n\n<span style=\"color:#66d9ef\">interface</span> <span style=\"color:#a6e22e\">MessagePart</span> {\n  <span style=\"color:#66d9ef\">type</span><span style=\"color:#f92672\">:</span> <span style=\"color:#66d9ef\">string</span>;\n  <span style=\"color:#a6e22e\">text?</span>: <span style=\"color:#66d9ef\">string</span>;\n}\n\n<span style=\"color:#66d9ef\">interface</span> <span style=\"color:#a6e22e\">MessageDetailResponse</span> {\n  <span style=\"color:#a6e22e\">parts?</span>: <span style=\"color:#66d9ef\">MessagePart</span>[];\n}\n\n<span style=\"color:#66d9ef\">interface</span> <span style=\"color:#a6e22e\">SessionIdleEvent</span> {\n  <span style=\"color:#66d9ef\">type</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;session.idle&#34;</span>;\n  <span style=\"color:#a6e22e\">properties</span><span style=\"color:#f92672\">?:</span> { <span style=\"color:#a6e22e\">sessionID</span>: <span style=\"color:#66d9ef\">string</span> };\n}\n\n<span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">escapeTelegramMarkdown</span>(<span style=\"color:#a6e22e\">text</span>: <span style=\"color:#66d9ef\">string</span>)<span style=\"color:#f92672\">:</span> <span style=\"color:#66d9ef\">string</span> {\n  <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">text</span>\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\\\/g</span>, <span style=\"color:#e6db74\">&#39;\\\\\\\\&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/_/g</span>, <span style=\"color:#e6db74\">&#39;\\\\_&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\*/g</span>, <span style=\"color:#e6db74\">&#39;\\\\*&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/`/g</span>, <span style=\"color:#e6db74\">&#39;\\\\`&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\[/g</span>, <span style=\"color:#e6db74\">&#39;\\\\[&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\]/g</span>, <span style=\"color:#e6db74\">&#39;\\\\]&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\(/g</span>, <span style=\"color:#e6db74\">&#39;\\\\(&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\)/g</span>, <span style=\"color:#e6db74\">&#39;\\\\)&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/~/g</span>, <span style=\"color:#e6db74\">&#39;\\\\~&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/&gt;/g</span>, <span style=\"color:#e6db74\">&#39;\\\\&gt;&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/#/g</span>, <span style=\"color:#e6db74\">&#39;\\\\#&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\+/g</span>, <span style=\"color:#e6db74\">&#39;\\\\+&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/-/g</span>, <span style=\"color:#e6db74\">&#39;\\\\-&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/=/g</span>, <span style=\"color:#e6db74\">&#39;\\\\=&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\|/g</span>, <span style=\"color:#e6db74\">&#39;\\\\|&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/{/g</span>, <span style=\"color:#e6db74\">&#39;\\\\{&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/}/g</span>, <span style=\"color:#e6db74\">&#39;\\\\}&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/\\./g</span>, <span style=\"color:#e6db74\">&#39;\\\\.&#39;</span>)\n    .<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/!/g</span>, <span style=\"color:#e6db74\">&#39;\\\\!&#39;</span>);\n}\n\n<span style=\"color:#66d9ef\">export</span> <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">TelegramNotifyPlugin</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">async</span> ({ <span style=\"color:#a6e22e\">client</span> }<span style=\"color:#f92672\">:</span> { <span style=\"color:#a6e22e\">client</span>: <span style=\"color:#66d9ef\">OpenCodeClient</span> }) <span style=\"color:#f92672\">=&gt;</span> {\n  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">token</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">env</span>.<span style=\"color:#a6e22e\">OP_TELEGRAM_TOKEN</span>;\n  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">chatId</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">env</span>.<span style=\"color:#a6e22e\">OP_TELEGRAM_CHAT_ID</span>;\n\n  <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">token</span> <span style=\"color:#f92672\">||</span> <span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">chatId</span>) {\n    <span style=\"color:#a6e22e\">console</span>.<span style=\"color:#a6e22e\">error</span>(<span style=\"color:#e6db74\">&#39;[TelegramNotifyPlugin] Missing OP_TELEGRAM_TOKEN or OP_TELEGRAM_CHAT_ID, notifications disabled&#39;</span>);\n    <span style=\"color:#66d9ef\">return</span> { <span style=\"color:#a6e22e\">event</span>: <span style=\"color:#66d9ef\">async</span> () <span style=\"color:#f92672\">=&gt;</span> {} };\n  }\n\n  <span style=\"color:#66d9ef\">return</span> {\n    <span style=\"color:#a6e22e\">event</span>: <span style=\"color:#66d9ef\">async</span> ({ <span style=\"color:#a6e22e\">event</span> }) <span style=\"color:#f92672\">=&gt;</span> {\n      <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">event</span>.<span style=\"color:#66d9ef\">type</span> <span style=\"color:#f92672\">===</span> <span style=\"color:#e6db74\">&#34;session.idle&#34;</span>) {\n        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">sid</span> <span style=\"color:#f92672\">=</span> (<span style=\"color:#a6e22e\">event</span> <span style=\"color:#66d9ef\">as</span> <span style=\"color:#a6e22e\">SessionIdleEvent</span>).<span style=\"color:#a6e22e\">properties</span><span style=\"color:#f92672\">?</span>.<span style=\"color:#a6e22e\">sessionID</span>;\n        <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">sid</span>) <span style=\"color:#66d9ef\">return</span>;\n\n        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">text</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">getLastMessageContent</span>(<span style=\"color:#a6e22e\">client</span>, <span style=\"color:#a6e22e\">sid</span>);\n        <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">text</span>) <span style=\"color:#66d9ef\">return</span>;\n\n        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">prefix</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#e6db74\">&#34;✅ *OpenCode session finished:*&#34;</span>;\n        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">suffix</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#e6db74\">&#34;\\n\\n\\\\.\\\\.\\\\.read full message in OpenCode&#34;</span>;\n        \n        <span style=\"color:#75715e\">// Calculate max length for raw text (accounting for prefix and suffix)\n</span><span style=\"color:#75715e\"></span>        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">maxRawTextLength</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">TELEGRAM_MAX_LENGTH</span> <span style=\"color:#f92672\">-</span> <span style=\"color:#a6e22e\">prefix</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">-</span> <span style=\"color:#a6e22e\">suffix</span>.<span style=\"color:#a6e22e\">length</span>;\n        \n        <span style=\"color:#75715e\">// Truncate raw text BEFORE escaping to avoid breaking Markdown entities\n</span><span style=\"color:#75715e\"></span>        <span style=\"color:#66d9ef\">let</span> <span style=\"color:#a6e22e\">truncatedText</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">text</span>;\n        <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">truncatedText</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">&gt;</span> <span style=\"color:#a6e22e\">maxRawTextLength</span>) {\n          <span style=\"color:#a6e22e\">truncatedText</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">truncatedText</span>.<span style=\"color:#a6e22e\">slice</span>(<span style=\"color:#ae81ff\">0</span>, <span style=\"color:#a6e22e\">maxRawTextLength</span>);\n        }\n        \n        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">finalEscapedText</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">escapeTelegramMarkdown</span>(<span style=\"color:#a6e22e\">truncatedText</span>);\n        <span style=\"color:#66d9ef\">let</span> <span style=\"color:#a6e22e\">finalText</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#e6db74\">`</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">prefix</span><span style=\"color:#e6db74\">}</span><span style=\"color:#960050;background-color:#1e0010\">\\</span><span style=\"color:#e6db74\">n</span><span style=\"color:#960050;background-color:#1e0010\">\\</span><span style=\"color:#e6db74\">n</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">finalEscapedText</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">`</span>;\n        \n        <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">text</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">&gt;</span> <span style=\"color:#a6e22e\">maxRawTextLength</span>) {\n          <span style=\"color:#a6e22e\">finalText</span> <span style=\"color:#f92672\">+=</span> <span style=\"color:#a6e22e\">suffix</span>;\n        }\n\n        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">payload</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">JSON</span>.<span style=\"color:#a6e22e\">stringify</span>({ \n          <span style=\"color:#a6e22e\">chat_id</span>: <span style=\"color:#66d9ef\">chatId</span>, \n          <span style=\"color:#a6e22e\">text</span>: <span style=\"color:#66d9ef\">finalText</span>,\n          <span style=\"color:#a6e22e\">parse_mode</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;MarkdownV2&#34;</span>\n        });\n\n        <span style=\"color:#a6e22e\">addPendingOperation</span>(<span style=\"color:#a6e22e\">sendTelegramMessage</span>(<span style=\"color:#a6e22e\">token</span>, <span style=\"color:#a6e22e\">payload</span>));\n      }\n    },\n  };\n};\n\n<span style=\"color:#66d9ef\">async</span> <span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">getLastMessageContent</span>(<span style=\"color:#a6e22e\">client</span>: <span style=\"color:#66d9ef\">OpenCodeClient</span>, <span style=\"color:#a6e22e\">sessionId</span>: <span style=\"color:#66d9ef\">string</span>)<span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">Promise</span>&lt;<span style=\"color:#f92672\">string</span> <span style=\"color:#960050;background-color:#1e0010\">|</span> <span style=\"color:#a6e22e\">null</span>&gt; {\n  <span style=\"color:#66d9ef\">try</span> {\n    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">listResponse</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">client</span>.<span style=\"color:#a6e22e\">_client</span>.<span style=\"color:#a6e22e\">request</span>({\n      <span style=\"color:#a6e22e\">method</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#39;GET&#39;</span>,\n      <span style=\"color:#a6e22e\">url</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">`/session/</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">sessionId</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">/message`</span>,\n    });\n\n    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">messages</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">listResponse</span><span style=\"color:#f92672\">?</span>.<span style=\"color:#a6e22e\">data</span> <span style=\"color:#f92672\">||</span> [];\n    <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">messages</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">===</span> <span style=\"color:#ae81ff\">0</span>) <span style=\"color:#66d9ef\">return</span> <span style=\"color:#66d9ef\">null</span>;\n\n    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">lastMsgId</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">messages</span>[<span style=\"color:#a6e22e\">messages</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">-</span> <span style=\"color:#ae81ff\">1</span>]<span style=\"color:#f92672\">?</span>.<span style=\"color:#a6e22e\">info</span><span style=\"color:#f92672\">?</span>.<span style=\"color:#a6e22e\">id</span>;\n    <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">lastMsgId</span>) <span style=\"color:#66d9ef\">return</span> <span style=\"color:#66d9ef\">null</span>;\n\n    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">msgResponse</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">client</span>.<span style=\"color:#a6e22e\">_client</span>.<span style=\"color:#a6e22e\">request</span>({\n      <span style=\"color:#a6e22e\">method</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#39;GET&#39;</span>,\n      <span style=\"color:#a6e22e\">url</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">`/session/</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">sessionId</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">/message/</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">lastMsgId</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">`</span>,\n    });\n\n    <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">extractTextFromParts</span>((<span style=\"color:#a6e22e\">msgResponse</span> <span style=\"color:#66d9ef\">as</span> <span style=\"color:#66d9ef\">any</span>).<span style=\"color:#a6e22e\">data</span> <span style=\"color:#f92672\">||</span> <span style=\"color:#a6e22e\">msgResponse</span>);\n  } <span style=\"color:#66d9ef\">catch</span> (<span style=\"color:#a6e22e\">error</span>) {\n    <span style=\"color:#a6e22e\">console</span>.<span style=\"color:#a6e22e\">error</span>(<span style=\"color:#e6db74\">&#39;[TelegramNotifyPlugin] Failed to fetch message:&#39;</span>, <span style=\"color:#a6e22e\">error</span>);\n    <span style=\"color:#66d9ef\">return</span> <span style=\"color:#66d9ef\">null</span>;\n  }\n}\n\n<span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">extractTextFromParts</span>(<span style=\"color:#a6e22e\">msgData</span>: <span style=\"color:#66d9ef\">MessageDetailResponse</span>)<span style=\"color:#f92672\">:</span> <span style=\"color:#66d9ef\">string</span> <span style=\"color:#f92672\">|</span> <span style=\"color:#66d9ef\">null</span> {\n  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">parts</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">msgData</span><span style=\"color:#f92672\">?</span>.<span style=\"color:#a6e22e\">parts</span> <span style=\"color:#f92672\">||</span> [];\n  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">textContent</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">parts</span>\n    .<span style=\"color:#a6e22e\">filter</span>(<span style=\"color:#a6e22e\">p</span> <span style=\"color:#f92672\">=&gt;</span> <span style=\"color:#a6e22e\">p</span>.<span style=\"color:#66d9ef\">type</span> <span style=\"color:#f92672\">!==</span> <span style=\"color:#e6db74\">&#34;reasoning&#34;</span> <span style=\"color:#f92672\">&amp;&amp;</span> <span style=\"color:#a6e22e\">p</span>.<span style=\"color:#66d9ef\">type</span> <span style=\"color:#f92672\">!==</span> <span style=\"color:#e6db74\">&#34;thought&#34;</span> <span style=\"color:#f92672\">&amp;&amp;</span> <span style=\"color:#a6e22e\">p</span>.<span style=\"color:#a6e22e\">text</span>)\n    .<span style=\"color:#a6e22e\">map</span>(<span style=\"color:#a6e22e\">p</span> <span style=\"color:#f92672\">=&gt;</span> <span style=\"color:#a6e22e\">p</span>.<span style=\"color:#a6e22e\">text</span>)\n    .<span style=\"color:#a6e22e\">join</span>(<span style=\"color:#e6db74\">&#34;\\n\\n&#34;</span>);\n  \n  <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">textContent</span>.<span style=\"color:#a6e22e\">length</span> <span style=\"color:#f92672\">&gt;</span> <span style=\"color:#a6e22e\">MIN_TEXT_LENGTH</span> <span style=\"color:#f92672\">?</span> <span style=\"color:#a6e22e\">textContent</span> : <span style=\"color:#66d9ef\">null</span>;\n}\n\n<span style=\"color:#66d9ef\">async</span> <span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">sendTelegramMessage</span>(<span style=\"color:#a6e22e\">token</span>: <span style=\"color:#66d9ef\">string</span>, <span style=\"color:#a6e22e\">payload</span>: <span style=\"color:#66d9ef\">string</span>)<span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">Promise</span>&lt;<span style=\"color:#f92672\">void</span>&gt; {\n  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">controller</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">AbortController</span>();\n  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">timeout</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">setTimeout</span>(() <span style=\"color:#f92672\">=&gt;</span> <span style=\"color:#a6e22e\">controller</span>.<span style=\"color:#a6e22e\">abort</span>(), <span style=\"color:#a6e22e\">TELEGRAM_TIMEOUT_MS</span>);\n\n  <span style=\"color:#66d9ef\">try</span> {\n    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">response</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">fetch</span>(<span style=\"color:#e6db74\">`https://api.telegram.org/bot</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">token</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">/sendMessage`</span>, {\n      <span style=\"color:#a6e22e\">method</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;POST&#34;</span>,\n      <span style=\"color:#a6e22e\">headers</span><span style=\"color:#f92672\">:</span> { <span style=\"color:#e6db74\">&#34;Content-Type&#34;</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;application/json&#34;</span> },\n      <span style=\"color:#a6e22e\">body</span>: <span style=\"color:#66d9ef\">payload</span>,\n      <span style=\"color:#a6e22e\">signal</span>: <span style=\"color:#66d9ef\">controller.signal</span>\n    });\n\n    <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">response</span>.<span style=\"color:#a6e22e\">ok</span>) {\n      <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">errorText</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">response</span>.<span style=\"color:#a6e22e\">text</span>();\n      <span style=\"color:#a6e22e\">console</span>.<span style=\"color:#a6e22e\">error</span>(<span style=\"color:#e6db74\">`[TelegramNotifyPlugin] Telegram API error: </span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">response</span>.<span style=\"color:#a6e22e\">status</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">`</span>, <span style=\"color:#a6e22e\">errorText</span>);\n    }\n  } <span style=\"color:#66d9ef\">catch</span> (<span style=\"color:#a6e22e\">error</span>) {\n    <span style=\"color:#a6e22e\">console</span>.<span style=\"color:#a6e22e\">error</span>(<span style=\"color:#e6db74\">&#39;[TelegramNotifyPlugin] Telegram notification failed:&#39;</span>, <span style=\"color:#a6e22e\">error</span>);\n  } <span style=\"color:#66d9ef\">finally</span> {\n    <span style=\"color:#a6e22e\">clearTimeout</span>(<span style=\"color:#a6e22e\">timeout</span>);\n  }\n}\n\n</code></pre></div><p>Просто кладем в ~/.config/opencode/plugins/telegram.ts , перезапускаем опенкод и смотрим сообщения в своей телеге :)</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/photo-2026-03-03-18-10-24.jpg\" width=\"289\" height=\"640\" alt=\"\">\n<p>В ближайшее время думаю потестить и пофиксить транкейт, а так же подумать над обратной связью, и возможностью отправлять сообщение через бота (полюбому надо делать это секьюрно))</p>\n<p>UPD: Дело было не в транкейте, с ним все ок. Вобщем плагин иногда не работал совершенно в рандомных случаях. Оказалось опенкод иногда завершал процесс до того как отошлется сообщение в телегу через обычный async/await. Пришлось (как я понял 😅) сделать pendingOperations промис, в который кладутся незавершенные операции, и добавлять его в основной процесс отслеживая событие beforeExit\n.\nНу и телега иногда возвращала 400, на нечитаемые символы для Markdown, сделал эскейпинг для таких символов.</p>\n<p>Сейчас плагин вроде работет норм, код в статье обновил на актуальный :)</p>\n",
				
				"date_published": "2026-03-03T22:58:00+08:00",
				"url": "https://ai-vs-devops.micro.blog/2026/03/03/claude-code-opencode-telegram.html"
			},
			{
				"id": "http://ai-vs-devops.micro.blog/2026/01/12/cursor-commands.html",
				"title": "Cursor commands. Бустим качество работы в Курсоре.",
				"content_html": "<p>Я частенько встречаю, что в проектах уже используют .cursor/rules, а вот .cursor/commands - не часто. Между тем это отличный инструмент для повышения скорости и качества своей работы.</p>\n<p>Плюс команд в курсоре, что их не надо добавлять в каждый репозиторий и менеджить везде отдельно, курсор подтягивает все команды из текущего воркспейса. То есть достаточно положить их в одну репу, а дальше можно использовать для работы со всеми своими открытыми репами.\nДостаточно положить файлы ниже в .cursor/commands/, в окне чата набрать &ldquo;/&rdquo; и выбрать нужную команду. См. скрин ниже:</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/screenshot-from-2026-01-12-15-24-28.png\" width=\"600\" height=\"194\" alt=\"\">\n<h3 id=\"дальше-мое-мнение-по-каждому-представленной-здесь-команде-ну-и-сам-код-забирайте-пользуйтесь-\">Дальше мое мнение по каждому представленной здесь команде, ну и сам код. Забирайте, пользуйтесь ;)</h3>\n<p><strong>explain-code.md</strong>: объясняет выделенный участок кода (или переданный в чат). Да, конечно можно просто скопировать строки кода и спросить &ldquo;What this code does?&rdquo;. Но результат будет непредсказуемым. Иногда все хорошо и понятно, иногда неполно и недостаточно, придется задавать наводящие вопросы и дальше тратить токены. Вся прелесть курсор команд в том, что мы создаем predefined формат вопроса, очень подробный и с полностью предсказуемым результатом, и запускаем одной кнопкой. В данном случае результат всегда будет содержать важные пояснения по участкам кода, логику, используемые паттерны, рекомендации. Удобно!</p>\n<p><strong>code-review.md</strong>: делаем подробное код ревью. Команда сразу берет незакомиченые файлы через git diff, или закомиченые новые изменения в текущем бранче, не надо передавать все файлы и сттроки в контекст. Ревью подробное, выдает что ок, где были найдены issues и их severity (critical, medium, low), рассказывает про Refactoring opportunities.</p>\n<p><strong>onboarding-plan.md</strong>: вызываем команду и пишем что-то о себе (I&rsquo;m a devops engineer, highly experienced in containers, compose, k8s. или I&rsquo;m a senior python developer slightly familiar with AI-based products development) и получаем релевантную для себя онбординг информацию по проекту. Что полезно лично для моей роли, какие доки читать, как застепатить локальный энвайронмент итд итп. Все с подробным планом по дням.</p>\n<p><strong>commit-message.md/pr-description.md</strong>: если ваши комиты выглядят как fix1, fix2, fix3finalfinal, а в PR всегда написано &ldquo;Closes #1234&rdquo; - то эта штука для вас. Проверяет изменения в файлах в стейдже или что поменялось в бранче и выдает релевантное и подробное сообщение, которое остается просто вставить в  git commit -m &quot;&quot; или в PR вашего гитхаба/гитлаба.</p>\n<h3 id=\"explain-codemd\">explain-code.md</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-markdown\" data-lang=\"markdown\">---\ndescription: &#39;Explain selected code or file in detail&#39;\nargument-hint: [relevant code or file]\n---\n\n<span style=\"color:#75715e\">## Role\n</span><span style=\"color:#75715e\"></span>\nYou&#39;re a senior developer explaining code to a colleague. Be clear, thorough, and pedagogical.\n\n<span style=\"color:#75715e\">## Task\n</span><span style=\"color:#75715e\"></span>\nExplain the code that the user has selected or referenced by:\n\n<span style=\"color:#66d9ef\">1.</span> Describing what the code does at a high level\n<span style=\"color:#66d9ef\">2.</span> Breaking down the key components and logic flow\n<span style=\"color:#66d9ef\">3.</span> Explaining any complex patterns or algorithms\n<span style=\"color:#66d9ef\">4.</span> Highlighting important details or gotchas\n<span style=\"color:#66d9ef\">5.</span> Providing context about why it might be designed this way\n\n<span style=\"color:#75715e\">## Explanation Structure\n</span><span style=\"color:#75715e\"></span>\n<span style=\"color:#75715e\">### Overview\n</span><span style=\"color:#75715e\"></span>Start with a concise summary of what this code does\n\n<span style=\"color:#75715e\">### Key Components\n</span><span style=\"color:#75715e\"></span>Break down the main parts:\n<span style=\"color:#66d9ef\">-</span> Functions/classes and their responsibilities\n<span style=\"color:#66d9ef\">-</span> Important variables or state\n<span style=\"color:#66d9ef\">-</span> External dependencies\n\n<span style=\"color:#75715e\">### Logic Flow\n</span><span style=\"color:#75715e\"></span>Explain the execution path:\n<span style=\"color:#66d9ef\">-</span> What happens step by step\n<span style=\"color:#66d9ef\">-</span> Decision points and branches\n<span style=\"color:#66d9ef\">-</span> Error handling\n\n<span style=\"color:#75715e\">### Notable Patterns\n</span><span style=\"color:#75715e\"></span>Identify any design patterns, idioms, or techniques:\n<span style=\"color:#66d9ef\">-</span> Why they&#39;re used here\n<span style=\"color:#66d9ef\">-</span> Benefits and trade-offs\n\n<span style=\"color:#75715e\">### Gotchas &amp; Edge Cases\n</span><span style=\"color:#75715e\"></span>Point out:\n<span style=\"color:#66d9ef\">-</span> Potential pitfalls\n<span style=\"color:#66d9ef\">-</span> Edge cases being handled\n<span style=\"color:#66d9ef\">-</span> Things that might be non-obvious\n\n<span style=\"color:#75715e\">### Context\n</span><span style=\"color:#75715e\"></span>If relevant:\n<span style=\"color:#66d9ef\">-</span> Why this approach was chosen\n<span style=\"color:#66d9ef\">-</span> Alternative approaches\n<span style=\"color:#66d9ef\">-</span> How it fits into the larger system\n\n<span style=\"color:#75715e\">## Guidelines\n</span><span style=\"color:#75715e\"></span>\n<span style=\"color:#66d9ef\">-</span> Use analogies when helpful\n<span style=\"color:#66d9ef\">-</span> Provide examples for complex concepts\n<span style=\"color:#66d9ef\">-</span> Link to documentation for specialized terms\n<span style=\"color:#66d9ef\">-</span> Be specific with technical details\n<span style=\"color:#66d9ef\">-</span> Don&#39;t assume prior knowledge of the codebase\n<span style=\"color:#66d9ef\">-</span> Highlight both good practices and potential improvements\n\n</code></pre></div><h3 id=\"code-reviewmd\">code-review.md</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-markdown\" data-lang=\"markdown\">---\n\ndescription: &#39;Perform comprehensive code review with structured feedback&#39;\n\ntools: [&#39;changes&#39;]\n\n---\n\n\n\n<span style=\"color:#75715e\">## Role\n</span><span style=\"color:#75715e\"></span>\n\n\nYou&#39;re a senior software engineer conducting a thorough code review with focus on code quality, maintainability, and best practices.\n\n\n\n<span style=\"color:#75715e\">## Task\n</span><span style=\"color:#75715e\"></span>\n\n\nReview the code changes by:\n\n\n\n<span style=\"color:#66d9ef\">1.</span> Running <span style=\"color:#e6db74\">`git diff`</span> to analyze uncommitted changes, or\n\n<span style=\"color:#66d9ef\">2.</span> Running <span style=\"color:#e6db74\">`git diff main...HEAD`</span> to review branch changes\n\n<span style=\"color:#66d9ef\">3.</span> Providing structured, actionable feedback\n\n\n\n<span style=\"color:#75715e\">## Review Criteria\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#75715e\">### 1. Code Quality\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> **Readability**: Is the code easy to understand?\n\n<span style=\"color:#66d9ef\">-</span> **Naming**: Are variables, functions, and classes well-named?\n\n<span style=\"color:#66d9ef\">-</span> **Complexity**: Can any complex logic be simplified?\n\n<span style=\"color:#66d9ef\">-</span> **DRY principle**: Is there unnecessary duplication?\n\n\n\n<span style=\"color:#75715e\">### 2. Best Practices\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> **Error handling**: Are errors handled appropriately?\n\n<span style=\"color:#66d9ef\">-</span> **Type safety**: Are types used correctly (TypeScript/typed languages)?\n\n<span style=\"color:#66d9ef\">-</span> **Security**: Are there potential security vulnerabilities?\n\n<span style=\"color:#66d9ef\">-</span> **Performance**: Are there obvious performance issues?\n\n\n\n<span style=\"color:#75715e\">### 3. Architecture\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> **Separation of concerns**: Is logic properly separated?\n\n<span style=\"color:#66d9ef\">-</span> **Single responsibility**: Does each function/class do one thing?\n\n<span style=\"color:#66d9ef\">-</span> **Dependencies**: Are dependencies managed well?\n\n<span style=\"color:#66d9ef\">-</span> **Reusability**: Can components be reused?\n\n\n\n<span style=\"color:#75715e\">### 4. Testing\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> **Test coverage**: Are critical paths tested?\n\n<span style=\"color:#66d9ef\">-</span> **Edge cases**: Are edge cases handled and tested?\n\n<span style=\"color:#66d9ef\">-</span> **Test quality**: Are tests meaningful and maintainable?\n\n\n\n<span style=\"color:#75715e\">### 5. Documentation\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> **Comments**: Are complex parts documented?\n\n<span style=\"color:#66d9ef\">-</span> **Type annotations**: Are function signatures documented?\n\n<span style=\"color:#66d9ef\">-</span> **README updates**: Does documentation reflect changes?\n\n\n\n<span style=\"color:#75715e\">## Output Format\n</span><span style=\"color:#75715e\"></span>\n\n\nProvide feedback in this structure:\n\n\n\n<span style=\"color:#75715e\">### ✅ Strengths\n</span><span style=\"color:#75715e\"></span>\n\n\nList what&#39;s done well (be specific)\n\n\n\n<span style=\"color:#75715e\">### ⚠️ Issues Found\n</span><span style=\"color:#75715e\"></span>\n\n\nFor each issue:\n\n\n\n<span style=\"color:#66d9ef\">-</span> **Severity**: 🔴 Critical | 🟡 Medium | 🔵 Low\n\n<span style=\"color:#66d9ef\">-</span> **Location**: File and line numbers\n\n<span style=\"color:#66d9ef\">-</span> **Problem**: Clear description\n\n<span style=\"color:#66d9ef\">-</span> **Suggestion**: Specific fix with code example\n\n<span style=\"color:#66d9ef\">-</span> **Rationale**: Why this matters\n\n\n\n<span style=\"color:#75715e\">### 🔧 Refactoring Opportunities\n</span><span style=\"color:#75715e\"></span>\n\n\nOptional improvements that would enhance code quality\n\n\n\n<span style=\"color:#75715e\">### 📚 Learning Resources\n</span><span style=\"color:#75715e\"></span>\n\n\nRelevant documentation or best practices (if applicable)\n\n\n\n<span style=\"color:#75715e\">### Summary\n</span><span style=\"color:#75715e\"></span>\n\n\nOverall assessment and recommended next steps\n\n\n\n<span style=\"color:#75715e\">## Guidelines\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> Be constructive and educational, not just critical\n\n<span style=\"color:#66d9ef\">-</span> Provide code examples for suggestions\n\n<span style=\"color:#66d9ef\">-</span> Prioritize issues by severity\n\n<span style=\"color:#66d9ef\">-</span> Explain <span style=\"font-style:italic\">*why*</span> something is an issue\n\n<span style=\"color:#66d9ef\">-</span> Consider the context and project requirements\n\n<span style=\"color:#66d9ef\">-</span> For TypeScript/React projects, focus on type safety and component patterns\n\n<span style=\"color:#66d9ef\">-</span> For Python projects, follow PEP 8 and type hints\n\n<span style=\"color:#66d9ef\">-</span> For Node.js projects, focus on async handling and error management\n\n</code></pre></div><h3 id=\"onboarding-planmd\">onboarding-plan.md</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-markdown\" data-lang=\"markdown\">---\n\ndescription: &#39;Help new team members onboard with a phased plan and suggestions for first tasks.&#39;\n\n---\n\n\n\n# Create My Onboarding Plan\n\n\n\nI&#39;m a new team member joining this project and I need help creating a structured onboarding plan.\n\n\n\nMy background My background will be specified at the end of the message. If I didn&#39;t specify the background, please ask me for the background before answering the question. \n\n\n\nPlease create a personalized phased onboarding plan that includes the following phases.\n\n\n\n<span style=\"color:#75715e\">## Phase 1 - Foundation\n</span><span style=\"color:#75715e\"></span>\n\n\nEnvironment setup with step-by-step instructions and troubleshooting tips, plus identifying the most important documentation to read first\n\n\n\n<span style=\"color:#75715e\">## Phase 2 - Exploration\n</span><span style=\"color:#75715e\"></span>\n\n\nCodebase discovery starting with README files, running existing tests/scripts to understand workflows, and finding beginner-friendly first tasks like documentation improvements. If possible, find me specific open issues or tasks that are suitable for my background.\n\n\n\n<span style=\"color:#75715e\">## Phase 3 - Integration\n</span><span style=\"color:#75715e\"></span>\n\n\nLearning team processes, making first contributions, and building confidence through early wins\n\n\n\nFor each phase, break down complex topics into manageable steps, recommend relevant resources, provide concrete next steps, and suggest hands-on practice over just reading theory.\n\n\n</code></pre></div><h3 id=\"commit-messagemd\">commit-message.md</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-markdown\" data-lang=\"markdown\">---\n\ndescription: &#39;Generate conventional commit messages from staged changes&#39;\n\nargument-hint: [relevant code or file]\n\n---\n\n\n\n<span style=\"color:#75715e\">## Role\n</span><span style=\"color:#75715e\"></span>\n\n\nYou&#39;re an expert at writing clear, descriptive commit messages following conventional commit standards.\n\n\n\n<span style=\"color:#75715e\">## Task\n</span><span style=\"color:#75715e\"></span>\n\n\nGenerate a commit message for the currently staged changes by:\n\n\n\n<span style=\"color:#66d9ef\">1.</span> Running <span style=\"color:#e6db74\">`git diff --staged`</span> to see the changes\n\n<span style=\"color:#66d9ef\">2.</span> Analyzing the modifications to understand their purpose\n\n<span style=\"color:#66d9ef\">3.</span> Creating a commit message following this format:\n\n\n\n\n\n&lt;<span style=\"color:#f92672\">type</span>&gt;(&lt;<span style=\"color:#f92672\">scope</span>&gt;): &lt;<span style=\"color:#f92672\">subject</span>&gt;\n\n\n\n&lt;<span style=\"color:#f92672\">body</span>&gt;\n\n\n\n&lt;<span style=\"color:#f92672\">footer</span>&gt;\n\n\n\n\n<span style=\"color:#75715e\">## Commit Type Guidelines\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> **feat**: New feature\n\n<span style=\"color:#66d9ef\">-</span> **fix**: Bug fix\n\n<span style=\"color:#66d9ef\">-</span> **docs**: Documentation changes\n\n<span style=\"color:#66d9ef\">-</span> **style**: Code style changes (formatting, semicolons, etc.)\n\n<span style=\"color:#66d9ef\">-</span> **refactor**: Code refactoring without functionality changes\n\n<span style=\"color:#66d9ef\">-</span> **perf**: Performance improvements\n\n<span style=\"color:#66d9ef\">-</span> **test**: Adding or updating tests\n\n<span style=\"color:#66d9ef\">-</span> **chore**: Build process or tooling changes\n\n<span style=\"color:#66d9ef\">-</span> **ci**: CI/CD changes\n\n\n\n<span style=\"color:#75715e\">## Requirements\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">1.</span> <span style=\"font-weight:bold\">**Subject line**</span> (50 chars max):\n\n   <span style=\"color:#66d9ef\">-</span> Use imperative mood (&#34;add&#34; not &#34;added&#34;)\n\n   <span style=\"color:#66d9ef\">-</span> Don&#39;t capitalize first letter\n\n   <span style=\"color:#66d9ef\">-</span> No period at end\n\n   \n\n<span style=\"color:#66d9ef\">2.</span> <span style=\"font-weight:bold\">**Body**</span> (optional, wrap at 72 chars):\n\n   <span style=\"color:#66d9ef\">-</span> Explain <span style=\"font-style:italic\">*what*</span> and <span style=\"font-style:italic\">*why*</span>, not <span style=\"font-style:italic\">*how*</span>\n\n   <span style=\"color:#66d9ef\">-</span> Include motivation for the change\n\n   <span style=\"color:#66d9ef\">-</span> Reference any breaking changes\n\n\n\n<span style=\"color:#66d9ef\">3.</span> <span style=\"font-weight:bold\">**Footer**</span> (optional):\n\n   <span style=\"color:#66d9ef\">-</span> Reference issue numbers (e.g., &#34;Fixes #123&#34;)\n\n   <span style=\"color:#66d9ef\">-</span> Note breaking changes with &#34;BREAKING CHANGE:&#34;\n\n\n\n<span style=\"color:#75715e\">## Output\n</span><span style=\"color:#75715e\"></span>\n\n\nProvide the commit message ready to use with <span style=\"color:#e6db74\">`git commit -m`</span>. The commit message should be rendered in markdown format.\n\n</code></pre></div><h3 id=\"pr-descriptionmd\">pr-description.md</h3>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4\"><code class=\"language-markdown\" data-lang=\"markdown\">---\n\ndescription: &#39;Generate concise and natural pull request descriptions&#39;\n\n---\n\n\n\n<span style=\"color:#75715e\">## Role\n</span><span style=\"color:#75715e\"></span>\n\n\nYou&#39;re a developer writing a clear and concise pull request description that sounds natural and human.\n\n\n\n<span style=\"color:#75715e\">## Task\n</span><span style=\"color:#75715e\"></span>\n\n\nGenerate a pull request description by:\n\n\n\n<span style=\"color:#66d9ef\">1.</span> Running <span style=\"color:#e6db74\">`git diff main...HEAD`</span> to see the changes\n\n<span style=\"color:#66d9ef\">2.</span> Understanding what was changed and why\n\n<span style=\"color:#66d9ef\">3.</span> Creating a natural, developer-style description\n\n\n\n<span style=\"color:#75715e\">## Requirements\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"color:#66d9ef\">-</span> Keep it concise and conversational\n\n<span style=\"color:#66d9ef\">-</span> Write like a real developer would\n\n<span style=\"color:#66d9ef\">-</span> Focus on what matters\n\n<span style=\"color:#66d9ef\">-</span> Reference issue numbers naturally (e.g., &#34;fixes #123&#34; or &#34;addresses #456&#34;)\n\n<span style=\"color:#66d9ef\">-</span> Avoid corporate jargon or overly formal language\n\n<span style=\"color:#66d9ef\">-</span> Don&#39;t use templates or sections unless the change is complex\n\n\n\n<span style=\"color:#75715e\">## Style Guidelines\n</span><span style=\"color:#75715e\"></span>\n\n\n<span style=\"font-weight:bold\">**Good examples:**</span>\n\n<span style=\"color:#66d9ef\">-</span> &#34;Fixed the auth redirect loop when session expires. The middleware wasn&#39;t checking token validity correctly.&#34;\n\n<span style=\"color:#66d9ef\">-</span> &#34;Added dark mode support. Users can toggle it in settings, and the preference is saved to localStorage.&#34;\n\n<span style=\"color:#66d9ef\">-</span> &#34;Refactored the validation logic to use Pydantic models. Much cleaner now and easier to test.&#34;\n\n\n\n<span style=\"font-weight:bold\">**Avoid:**</span>\n\n<span style=\"color:#66d9ef\">-</span> Overly formal: &#34;This PR implements feature X as per requirements...&#34;\n\n<span style=\"color:#66d9ef\">-</span> Too vague: &#34;Updated files&#34;\n\n<span style=\"color:#66d9ef\">-</span> Template speak: &#34;## Summary\\n## Changes\\n## Testing&#34;\n\n\n\n<span style=\"color:#75715e\">## Output\n</span><span style=\"color:#75715e\"></span>\n\n\nProvide a description ready to paste into the PR/MR description field.\n\n\n\nFor simple changes: 1-2 sentences\n\nFor complex changes: Brief paragraph + bullet points if needed\n\n</code></pre></div><p><i> Спасибо за внимание и подписывайтесь на телеграм канал <a href=\"https://t.me/ai_vs_devops\">https://t.me/ai_vs_devops</a> </i></p>\n",
				
				"date_published": "2026-01-12T23:27:22+08:00",
				"url": "https://ai-vs-devops.micro.blog/2026/01/12/cursor-commands.html"
			},
			{
				"id": "http://ai-vs-devops.micro.blog/2026/01/09/kiss-vs-dry.html",
				"title": "KISS vs DRY. Как не переусложнить свою инфраструктуру-как-код?",
				"content_html": "<p>Инфраструктура как код - это почти всегда сложный выбор, когда дело доходит до скейлинга. В туториалах все просто - вот создали S3 бакет, вот EC2 машину. Домашние пет проекты тоже могут жить на условном ECS или одной машине с компоузом. Когда же ты погружаешься в реальный крупный бизнес проект сразу появляются вопросы, как организовать код для десятков взаимозависимых сервисов, на трех энвайронментах, в нескольких регионах, а иногда и мультиклауд или легаси он-прем сервисы.</p>\n<p>Когда команды сталкиваются со скейлингом инфраструктуры - перво наперво возникает желание убрать повторения кода. Концепт DRY (Dont Repeat Yourself) пришел из software engineering, и надежно поселился в умах девопсов и системных инженеров. Разнообразные фреймворки для Terraform (такие как Terragrunt, Terraspace, Atmos etc.) предлагают единый менеджмент tf бэкендов, наследование метаданных, генерацию конфигураций  на лету. Сейчас многие команды берут в работу эти инструменты, только потому что это - &ldquo;best practises&rdquo;. И на словах все звучит круто:  &ldquo;Write your infrastructure once, reuse it everywhere, maintain it in one place, and scale effortlessly!&rdquo;.</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/terragrunt-inheritance.png\" width=\"600\" height=\"356\" alt=\"terragrunt hierarchy inheritance example\">\n<h3 id=\"но-какова-цена-использования-этих-изящных-инженерных-решений-в-реальном-бизнесе\">Но какова цена использования этих &ldquo;изящных инженерных решений&rdquo; в реальном бизнесе?</h3>\n<p>3 часа ночи, и прод упал&hellip; Вам нужно понять, почему Terraform пытается уничтожить и пересоздать вашу базу данных, и вам нужно понять это вот прямо сейчас.</p>\n<p>При использовании DRY-метода с Terragrunt и иерархическим наследованием вы не просто читаете код Terraform. Вы отслеживаете значения на нескольких уровнях: корневой файл terragrunt.hcl с базовыми конфигурациями, переопределения, специфичные для энва. Динамически генерируемые конфигурации бэкенда, абстракции модулей, которые вызывают другие модули. Переменные, каскадно передаваемые по цепочкам наследования.</p>\n<p>Откуда на самом деле взялось это значение конфигурации базы данных? Из глобальной конфигурации? Переопределение именно этого энва? Значение модуля по-умолчанию? Вам приходиться играть в детектива и искать root cause вместо того, чтобы решать проблему. Каждый уровень абстракции добавляет когнитивную нагрузку, когда вы меньше всего можете себе это позволить, во время стрессовых ситуаций в 3 часа ночи.</p>\n<p>Фундаментальная проблема заключается в том, что <strong>DRY-метод оптимизирует написание кода, а не его чтение и понимание под давлением.</strong></p>\n<p>Или на проект приходит новый сотрудник. Даем ему задачу поменять пару рулов в секьюрити группах. Вроде все просто?\nС тулами для DRY абстракций человеку надо не просто знать Terraform, но и изучить ваш tf фреймворк, понять иерархическую структуру конфигураций, как значения наследуются и переопределяются между слоями, и где можно вносить изменения, не нарушив работу других энвов.\nОнбординг превращается в передачу сакральных знаний. Работа, которая должна занимать часы, может занимать дни. Сравните это с тем, когда человек просто открыл структуру директорий и сразу увидел что как и куда деплоится? А это все разница в time-to-prod, выраженная в реальных человекочасах и деньгах.</p>\n<p>Ну и еще один пункт - vendor lock. Ваш код, ваша команда, ваши процессы, пайплайны и документация зависят от одного фреймворка. Когда терраформ добавляет новую фичу - вы просто ждете когда она станет доступна в вашем фреймворке. А смигрировать отсюда к другому подходу становится непосильной задачей.</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/iac-rotten.jpg\" width=\"600\" height=\"408\" alt=\"Auto-generated description: Buzz Lightyear and Woody from Toy Story are depicted with text saying, THIS IS YOUR INFRASTRUCTURE CODE, IT'S ROTTEN TO THE CORE.\">\n<h3 id=\"есть-ли-альтернатива\">Есть ли альтернатива?</h3>\n<p>Есть. И это Бритва Оккама, или Keep It Simple, Stupid (KISS). Или просто не тащить в проект модные фреймворки и &ldquo;бест практики&rdquo;, когда для них нет реальной необходимости! На 90%+ проектов со всеми задачами и скейлингом справится обычный базовый терраформ и пайплайн оркестрация в вашем Github/Gitlab.</p>\n<p>Да, сложность никуда не уходит. Как и факт того что у нас есть разные энвы, регионы, и между ними нужна координация. И вопрос не в том &ldquo;как нам минимизировать сложность&rdquo;, а в том &ldquo;куда нам переместить сложность, чтобы минимизировать значение time to business&rdquo;.</p>\n<p><strong>DRY метод:</strong> сложность в тулах абстракций и наследуемых конфигуарциях.</p>\n<p><strong>KISS метод:</strong> мы используем плоскую структуру кода без цепочек наследуемых конфигураций, а сложность перенесем в CI/CD пайплайны, где все можно удобно мониторить и дебажить.</p>\n<p>Вот пример простой плоской структуры Terraform кода:\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2026/flat-tf.png\" width=\"600\" height=\"673\" alt=\"Auto-generated description: A directory tree shows an organized structure of multiple projects such as dev, staging, and prod with subdirectories and various services including vpc, eks, rds, and mwaa, along with a section for us-east-1 in AWS and Azure, indicating infrastructure components.\"></p>\n<ul>\n<li>Можем разбивать как по сервисам, так и по логическим группам.</li>\n<li>У каждого сервиса свой state файл, isolated blast radius, нет проблем с дебагом огромных стейтов.</li>\n<li>Централизация модулей в одном месте</li>\n<li>Легкая навигация, Just grep it  (c)Nike ✔</li>\n</ul>\n<p>Каждая директория - это отдельный терраформ деплоймент. Открой aws/us-east-1/prod/eks/ и ты увидешь что конкретно задеплоено для EKS в это регионе и этом энве. Без наследований, автогенерации и прочей магии.</p>\n<p>Да, у нас везде повторяется конфгурация бэкендов, например</p>\n<pre tabindex=\"0\"><code># aws/core-infrastructure/prod/backend.tf\nterraform {\n  backend &quot;s3&quot; {\n    bucket         = &quot;myorg-terraform-state-prod&quot;\n    key            = &quot;core-infrastructure/terraform.tfstate&quot;\n    region         = &quot;us-east-1&quot;\n    encrypt        = true\n    dynamodb_table = &quot;terraform-state-lock-prod&quot;\n  }\n}\n</code></pre><p>Адептов DRY такое бесит :) Но я могу сказать, что ты всегда легко видешь в каком бакете лежит стейт этого сервиса, а в какой таблице стоит лок. Тебе не надо понимать логику динамической конфигурации бекенда.\nСтоимость повторения - 100 строк простого YAML-а для 20 энвов. Преимущество — мгновенное устранение неполадок, отсутствие когнитивной нагрузки и полная ясность того, что где находится. 100 строк кода vs 40 часов изучения terragrunt, где лучше ROI?</p>\n<h3 id=\"а-где-происходит-вся-магия-в-github-actions-пайплайнах\">А где происходит вся магия? В Github Actions пайплайнах.</h3>\n<p><strong>Pull Requests:</strong></p>\n<ul>\n<li>Авто-детектим энвайронмент по файловому пути</li>\n<li>Прогоняем terraform plan</li>\n<li>Прогоняем security/complience checks</li>\n<li>Выводим план в PR коменты и блочим мерж в случае падений</li>\n</ul>\n<p><strong>Main Branch:</strong></p>\n<ul>\n<li>авто-детект энва</li>\n<li>terraform apply с ручным апрувалом</li>\n<li>алерты в случае падений</li>\n<li>трекаем configuration drifts и создаем тикеты</li>\n</ul>\n<p><strong>Scheduled:</strong></p>\n<ul>\n<li>Сверяем состояние с кодом и делаем drift detection по всем энвам</li>\n<li>Алерты на непредвиденные состояния</li>\n</ul>\n<p>Мы получаем инфраструктуру, которую просто менеджить и расширять. В команду проще найти специалиста с глубоким знанием CI/CD, чем со знанием конкретного Terraform фреймворка. У команды уходит меньше времени на траблштутинг и больше времени на прямые business-values.</p>\n<h3 id=\"но-это-же-не-скейлится\">Но это же не скейлится?!</h3>\n<p>Скажите, как много вы работали на проектах, с сотнями энвайронментов? На десятки - скейлится вполне, при этом давая изолированные изменения и ограниченный бласт радиус. Часто команды думают про мифический скейл в будущем, но по факту получают излишнюю сложность уже сегодня, не доживая до того времени, когда можно извлечь реальную выгоду от абстракций IaC фреймворков.</p>\n<p><strong>Вам нужен KISS если:</strong></p>\n<ul>\n<li>Количество энвов измеряется единицами или десятками</li>\n<li>Меньше 50 инженеров в команде</li>\n<li>Невысокая change frequency (обычный паттерн - мы подняли инфраструктуру один раз и она не меняется)</li>\n<li>Цена ошибки в инфрастуктурных изменениях очень высока (высоконагруженый прод, регулируемые отрасли бизнеса)</li>\n<li>У нас нет полноценной platform-команды системных инженеров</li>\n</ul>\n<p><strong>Вам нужен DRY если:</strong></p>\n<ul>\n<li>У вас сотни энвайронментов</li>\n<li>У вас полноценная сильная команда platform инженеров, которые шарят в сложных абстракциях, как лось по кукурузе))</li>\n<li>Эта же команда может разрабатывать и поддерживать свои in-house решения поверх базовых tf фреймворков (на это есть люди и деньги)</li>\n<li>Вы строите infrastructure-as-a-product</li>\n</ul>\n<p>Вывод - на новых проектах начинай с простого. Строй постепенно, увеличивай сложность только когда для этого есть реальная необходимость. Думай о business values и не принимай решений, если они обусловлены только &ldquo;инженерной красотой&rdquo; этого решения. При прочих равных, чаще для всех будет выгоднее выбрать &ldquo;Old Boring Technology&rdquo;, вместо &ldquo;New Fancy Silver Bullet&rdquo;.</p>\n<p>И удачи на проектах ;)</p>\n<p><em>Спасибо что дочитали, подписывайтесь на tg канал <a href=\"https://t.me/ai_vs_devops\">https://t.me/ai_vs_devops</a> :) Следующей будет вторая часть статьи про AI-разработку со Spec Driven Development методологией.</em></p>\n",
				
				"date_published": "2026-01-09T20:47:03+08:00",
				"url": "https://ai-vs-devops.micro.blog/2026/01/09/kiss-vs-dry.html"
			},
			{
				"id": "http://ai-vs-devops.micro.blog/2025/12/28/spec-driven-development-part-sdd.html",
				"title": "SPEC DRIVEN DEVELOPMENT. Part 1. От Вайб Кодинга к SDD. Kiro.",
				"content_html": "<p>Вайб кодинг сейчас у всех на слуху. Вплоть до того что этот термин уже обрел мемность. Суть - разрабатываем софт в AI-enabled IDE (Cursor, Windsurf, Claude Code итд). Без планирования, one-shot only, accept everything :))</p>\n<p>И конечно же те самые &ldquo;Make no mistakes!&rdquo; (для новичков) или &ldquo;Fix it or you go to jail!&rdquo; (для про).</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-1.jpg\" width=\"600\" height=\"339\" alt=\"Auto-generated description: A person wearing headphones and sunglasses is seated at a gaming setup with multiple monitors, surrounded by gaming and tech accessories.\">\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-2.jpg\" width=\"600\" height=\"648\" alt=\"Auto-generated description: A muscular man is focused on working at a desk with multiple monitors, accompanied by a humorous text about building a billion-dollar SaaS.\">\n<p>Несмотря на мемы и шутейки, у вайбкодинга есть и плюсы:</p>\n<ol>\n<li>Zero friction. Тебе не надо нервничать над подготовкой и думать с  чего начать. Есть идея - сразу начинаем ее воплощать в код.</li>\n<li>Как следствие быстрый idea-to-code loop. Быстро написали, быстро поправили (то что очевидно), перешли к следующей итерации.</li>\n</ol>\n<p>А теперь минусы для серьезной разработки:</p>\n<ol>\n<li>Контекст живет только в этом одном чате.</li>\n<li>Нужно переобьяснять или суммаризировать для новых сессий.</li>\n<li>Не team-friendly процесс. И нет возможности его повторить достаточно идентично.</li>\n</ol>\n<p>Две основные боли: context state и change management.\nА что если мы перенес нас стейт из чата в репозиторий?</p>\n<p>Так мы переходим от Вайбов к Context rules.\nКонтекст рулы - это текстовые файлы для вашей AI IDE, которые живут в вашем репозитории.\nНапример .cursor/rules/agents.mdc или AGENTS.md. Они подхватываются в контекст всегда или по заданым условиям. И несут в себе описание работы с вашим конкретным проектом, бест практики для конкретного языка программирования или фреймворка ит.д.</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-3.png\" width=\"600\" height=\"549\" alt=\"Auto-generated description: A text document contains rules and descriptions for applying them in a project, as well as organized rule folders for different aspects of the project.\">\n<p>Плюсы:</p>\n<ol>\n<li>Контекст сохранен в репе. Можно переиспользовать между сессиями и делиться с коллегами.</li>\n<li>Делаем работу более предсказуемой.</li>\n<li>Не надо каждый раз обьяснять нюансы и бест практики именно вашего проекта.</li>\n</ol>\n<p>Но и у такого подхода есть минусы:</p>\n<ol>\n<li>Обновлять и поддерживать приходится вручную.</li>\n<li>Какие то рулы могут становиться неактуальными, легко об этом забыть.</li>\n<li>Для этих рулов нет стандартной структуры и синтаксиса.</li>\n</ol>\n<p>Чтобы исправить эти минусы: лучше структурировать правила, упростить поддержку, отображать эволюцию вашего проекта по мере его разработки возник концепт</p>\n<p>Memory Bank.</p>\n<p>По сути это все еще текстовые рул файлы, но в которые мы (а чаще агент) пишет все что происходит. Требования, задачи, имплементация. Активный контекст агента. Сейчас этот концепт появляется во многих AI IDE (Kilo, Cline, Antigravity+Vertex AI, скоро хотят добавить и в Курсор)</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-4.jpg\" width=\"600\" height=\"346\" alt=\"Auto-generated description: A flowchart depicts the relationship between files titled projectbrief.md, productContext.md, systemPatterns.md, techContext.md, activeContext.md, and progress.md, showing a hierarchical structure.\">\n<p>Итого:</p>\n<ol>\n<li>И контекст и задачи у нас наконец не в чате, а в репозитории.</li>\n<li>Работет с разными IDE и агентами. Стабильно, даже если чат крашнулся.</li>\n<li>Упрощает онбординг и коллаборацию на проекте, все легко шарят общий прогресс проекта.</li>\n</ol>\n<p>Но все еще есть минусы и проблемы:</p>\n<ol>\n<li>Этот процесс не так хорошо скейлится.</li>\n<li>Легко забыть обновить мемори банк своим прогрессом, получаем дрифт.</li>\n<li>Все еще нет унифицированной структуры между разными проектами.</li>\n</ol>\n<p>А что если и context и change management будет частью общего унифицированного workflow?\nТак и появился концепт Spec-Driven Development.</p>\n<p>Как видно на скрине - эта техника сейчас стоит в группе Assess на известном Tech Radar от компании Thoughtworks. То есть эту практику можно смело пробовать.\nХотя основные постулаты да и сам термин сейчас быстро развивается и меняется.</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-5.jpg\" width=\"600\" height=\"371\" alt=\"Auto-generated description: A website page features a chart illustrating Spec-driven development surrounded by text describing its concept and applications.\">\n<p>Основные принципы:</p>\n<ol>\n<li>Мы создаем полный &ldquo;spec&rdquo; до того как начинаем писать какой либо код.</li>\n<li>Этот спек - source of truth и для разработчиков и для агентов.</li>\n<li>Change Management становится first class project artifacts.</li>\n</ol>\n<p>Процесс разработки проекта или фичи становится повторяемым. Работа со спецификацией хорошо интегрируется в PR-ы, аудиты и архитектурные решения.\nКонечно же это не серебряная пуля, и все еще есть сложности:</p>\n<ol>\n<li>Все еще нужна дисциплина чтобы поддерживать спеки.</li>\n<li>Плохая спецификация -&gt; Плохое решение (известный shit in - shit out принцип :)</li>\n<li>Команда должна согласиться на общий для всех формат спеков.</li>\n</ol>\n<p>И решить эти проблемы призваны специальные тулы (или полноценные IDE), работающие в парадигме SDD.\nЗдесь и появляется Kiro. 14 июля 25г. Всеми любимый (или не очень:)) Amazon/AWS выпускается свою AI-powered IDE, в принципе клон VSCode, но с интегрированным Spec Mode.</p>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-6.jpg\" width=\"600\" height=\"322\" alt=\"Auto-generated description: Agentic AI development platform interface displaying options for downloading and watching demos, with a focus on task and requirements management.\">\n<p>В которой ты с помощью агента создаешь полную спецификацию и implementation plan своего проекта.\nПолучаем:</p>\n<ol>\n<li>Memory bank и спецификация по разработке фич в репозитории, но с нативной интеграцией в IDE и ее процессы.</li>\n<li>Системные промпты агентов заточены на этот процесс, общий универсальный формат спецификации.</li>\n<li>Хорошая работа с требованиями, мы можем подключать продукт овнеров для их разработки в понятном им формате. Дальше передавать процесс архитектам для создания и правок дизайна системы/фичи. Дальше разработчики могут давать фидбек по созданым задачам и начинать их имплементировать сразу в Kiro.</li>\n</ol>\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-7.jpg\" width=\"600\" height=\"314\" alt=\"Auto-generated description: A diagram illustrates a system with three main components: Memory bank, Agent, and Specs, featuring organized documents and code elements, along with a side panel from the Kiro IDE.\">\n<p>Вроде идеальное решение, но конечно же Vendor lock-in, и этот весь процесс будет работать только с Kiro. Хотя как мне кажется после написания требований, дизайна системы и списка задач - саму имплементацию каждой задачи можно делать и в любой сторонней IDE, задав ей правила как помечать то что реализовано и как давать фидбек.\n<img src=\"https://ai-vs-devops.micro.blog/uploads/2025/part1-8.jpg\" width=\"600\" height=\"315\" alt=\"Kiro interface preview\"></p>\n<p>В следующей статье я расскажу про свой опыт с Kiro. С реальной задачей - миграцией довольно крупного AI сервиса (docker compose на 1000+ строк, 40+ сервисов) на kubernetes helm. Как проходит весь процесс изнутри? Насколько прожорлив агент по токенам? Хватит ли бесплатного триала? Выводы и впечатления. А дальше еще будет 1-2 статьи про опенсорсные замены для Kiro.</p>\n<p>_С вас лайк-шер-репост-давите колокольчик :) Подписывайтесь вобщем) <a href=\"https://t.me/ai_vs_devops\">t.me/ai_vs_dev&hellip;</a> _</p>\n",
				
				"date_published": "2025-12-29T00:16:07+08:00",
				"url": "https://ai-vs-devops.micro.blog/2025/12/28/spec-driven-development-part-sdd.html"
			}
	]
}
