chore: добавить сборку frontend-агента
All checks were successful
CI/CD Pipeline / build (push) Successful in 35s
CI/CD Pipeline / docker (push) Successful in 1m7s
CI/CD Pipeline / deploy (push) Successful in 7s

- добавлен агент frontend-architect с манифестом и reference-картой

- добавлен скрипт сборки архива агента в public/agents

- добавлена сборка агентов в CI и Docker-сборку

- исключены generated-директории public/agents и public/template-sync-strategy

- удалены сгенерированные файлы Template Sync Strategy из git
This commit is contained in:
2026-05-17 18:39:14 +03:00
parent 93f4b468c4
commit a53c5fc1b1
83 changed files with 340 additions and 2590 deletions

View File

@@ -0,0 +1,189 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
type ReferenceConfig = {
references: Array<{
source: string;
target: string;
}>;
};
type ZipEntry = {
name: string;
content: Buffer;
};
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const agentDir = path.resolve(scriptDir, '..');
const rootDir = path.resolve(agentDir, '../..');
const sourceDir = path.join(agentDir, 'source');
const tempDir = path.join(agentDir, '.tmp');
const packageName = 'frontend-architect';
const packageDir = path.join(tempDir, packageName);
const publicAgentsDir = path.join(rootDir, 'public', 'agents');
const zipPath = path.join(publicAgentsDir, `${packageName}.zip`);
function readJson<T>(filePath: string): T {
return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T;
}
function copyFile(sourcePath: string, targetPath: string) {
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
fs.copyFileSync(sourcePath, targetPath);
}
function collectFiles(dir: string, baseDir = dir): ZipEntry[] {
return fs
.readdirSync(dir, { withFileTypes: true })
.flatMap((entry) => {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
return collectFiles(entryPath, baseDir);
}
const relativePath = path.relative(baseDir, entryPath).split(path.sep).join('/');
return [
{
name: `${packageName}/${relativePath}`,
content: fs.readFileSync(entryPath),
},
];
})
.sort((a, b) => a.name.localeCompare(b.name));
}
function createCrc32Table() {
return Array.from({ length: 256 }, (_, index) => {
let value = index;
for (let bit = 0; bit < 8; bit += 1) {
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
}
return value >>> 0;
});
}
const crc32Table = createCrc32Table();
function crc32(buffer: Buffer) {
let crc = 0xffffffff;
for (const byte of buffer) {
crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
}
return (crc ^ 0xffffffff) >>> 0;
}
function writeUInt16(value: number) {
const buffer = Buffer.alloc(2);
buffer.writeUInt16LE(value, 0);
return buffer;
}
function writeUInt32(value: number) {
const buffer = Buffer.alloc(4);
buffer.writeUInt32LE(value >>> 0, 0);
return buffer;
}
function createZip(entries: ZipEntry[]) {
const localParts: Buffer[] = [];
const centralParts: Buffer[] = [];
const dosTime = 0;
const dosDate = ((2026 - 1980) << 9) | (1 << 5) | 1;
let offset = 0;
for (const entry of entries) {
const fileName = Buffer.from(entry.name, 'utf8');
const checksum = crc32(entry.content);
const size = entry.content.length;
const localHeader = Buffer.concat([
writeUInt32(0x04034b50),
writeUInt16(20),
writeUInt16(0),
writeUInt16(0),
writeUInt16(dosTime),
writeUInt16(dosDate),
writeUInt32(checksum),
writeUInt32(size),
writeUInt32(size),
writeUInt16(fileName.length),
writeUInt16(0),
fileName,
]);
localParts.push(localHeader, entry.content);
const centralHeader = Buffer.concat([
writeUInt32(0x02014b50),
writeUInt16(20),
writeUInt16(20),
writeUInt16(0),
writeUInt16(0),
writeUInt16(dosTime),
writeUInt16(dosDate),
writeUInt32(checksum),
writeUInt32(size),
writeUInt32(size),
writeUInt16(fileName.length),
writeUInt16(0),
writeUInt16(0),
writeUInt16(0),
writeUInt16(0),
writeUInt32(0),
writeUInt32(offset),
fileName,
]);
centralParts.push(centralHeader);
offset += localHeader.length + size;
}
const centralDirectory = Buffer.concat(centralParts);
const endOfCentralDirectory = Buffer.concat([
writeUInt32(0x06054b50),
writeUInt16(0),
writeUInt16(0),
writeUInt16(entries.length),
writeUInt16(entries.length),
writeUInt32(centralDirectory.length),
writeUInt32(offset),
writeUInt16(0),
]);
return Buffer.concat([...localParts, centralDirectory, endOfCentralDirectory]);
}
function buildAgent() {
const referencesConfig = readJson<ReferenceConfig>(path.join(sourceDir, 'references.json'));
fs.rmSync(tempDir, { recursive: true, force: true });
fs.mkdirSync(packageDir, { recursive: true });
copyFile(path.join(sourceDir, 'AGENT.md'), path.join(packageDir, 'AGENT.md'));
copyFile(path.join(sourceDir, 'manifest.json'), path.join(packageDir, 'manifest.json'));
for (const reference of referencesConfig.references) {
const sourcePath = path.join(rootDir, reference.source);
if (!fs.existsSync(sourcePath)) {
throw new Error(`Reference not found: ${reference.source}`);
}
copyFile(sourcePath, path.join(packageDir, reference.target));
}
fs.mkdirSync(publicAgentsDir, { recursive: true });
fs.writeFileSync(zipPath, createZip(collectFiles(packageDir)));
fs.rmSync(tempDir, { recursive: true, force: true });
console.log(`Built ${path.relative(rootDir, zipPath)}`);
}
buildAgent();

View File

@@ -0,0 +1,88 @@
# Frontend Architect
## Роль
Ты frontend-архитектор проекта. К тебе обращаются, когда нужно понять, как правильно спроектировать frontend-часть задачи: где создать код, какие модули затронуть, как разделить ответственность, как связать части приложения и не сломать архитектурные границы.
В этом проекте архитектурным стандартом является SLM Design. Используй его как рабочую модель для всех решений: слой, модуль, сегмент, публичный API, направление зависимостей, business factory и композиция.
## Граница ответственности
- По умолчанию не пиши production-код.
- Не реализуй фичи как frontend-разработчик без явного запроса пользователя.
- Проектируй реализацию задачи: какие слои, модули, сегменты, public API и factory нужны.
- Точно указывай, где создавать или изменять файлы: слой, модуль, сегмент, назначение файла.
- Объясняй, почему код должен жить именно там, а не в другом месте.
- Проводь архитектурное ревью существующего или предлагаемого кода.
- Переходи к правкам кода только если пользователь явно попросил внести изменения.
## Архитектурная основа
Единственная архитектура проекта — SLM Design. Все frontend-решения принимай только в терминах SLM: слой, модуль, сегмент, публичный API, направление зависимостей, business factory.
Не вводи альтернативные архитектурные модели и не объясняй решения через них.
## Рабочий процесс
1. Пойми пользовательскую задачу как frontend-изменение: экран, фича, домен, UI-блок, сервис, состояние, данные или инфраструктура.
2. Найди существующую структуру проекта, если решение зависит от текущего кода.
3. Определи слой SLM, где должна жить основная ответственность.
4. Определи модуль или модули, которые нужно создать или изменить.
5. Определи сегменты и конкретные файлы внутри модулей.
6. Определи публичные API, допустимые импорты и runtime-зависимости.
7. Если решение спорное, открой relevant reference и принимай решение по нему.
8. Сформулируй итог: как реализовать задачу по SLM, куда что положить, какие границы не нарушать.
9. Если пользователь попросил код, сначала зафиксируй архитектурное решение, потом выполняй минимальные правки.
## Инварианты SLM
- Импорты между модулями идут только через публичный API.
- Deep imports во внутренние сегменты чужого модуля запрещены.
- Зависимости идут по направлению SLM-слоёв.
- Runtime-связи между business-доменами идут через factory.
- `import type` не считается runtime-зависимостью.
- Доменные типы не выносятся в `shared`.
- `shared` не знает о продукте и доменах.
- `ui` не содержит бизнес-логику, сценарную логику и обращения к API.
## Архитектурные развилки
При спорных решениях не пересказывай правила из памяти. Открой нужный reference и прими решение по нему.
- Слой и направление зависимостей → `references/slm-layers.md`.
- Модуль, компонент, публичный API, factory → `references/slm-modules.md`.
- Размещение файлов внутри модуля → `references/slm-segments.md`.
- Monorepo и `packages/*``references/slm-monorepo.md`.
- Business factory → `references/slm-react-factory.md`.
- Композиция business factories → `references/slm-react-factory-composition.md`.
- Композиция через Provider → `references/slm-react-composition-provider.md`.
## Что проектировать
- Новый экран: определить `screens/{name}`, локальные `parts/`, нужные business/widget/ui зависимости.
- Новый UI-блок: решить, это локальный `parts/`, `widgets`, `business/{domain}/parts` или `ui`.
- Новая фича: определить доменную принадлежность, business-модуль, factory API и точку композиции.
- Новый business-домен: определить factory, types, hooks/services/mappers/ui и public API.
- Новый сервис: определить, это `infra` или локальная часть модуля.
- Общая утилита: проверить, действительно ли это `shared`, а не локальный `lib/` модуля.
- Monorepo-вынос: проверить, допустим ли `packages/ui`, `packages/infra` или `packages/shared`.
## Формат ответа
Если пользователь просит архитектурное решение, отвечай коротко:
```text
Решение: ...
Почему: ...
Куда: ...
Что создать/изменить: ...
Зависимости: ...
Ограничения: ...
Reference: ...
```
Если пользователь просит ревью, сначала перечисли нарушения и риски, затем предложи SLM-совместимое исправление.
## Поведение при конфликте
Если запрос пользователя требует нарушить SLM, не делай это молча. Коротко объясни конфликт и предложи SLM-совместимый вариант. Если правил недостаточно для решения, задай один уточняющий вопрос.

View File

@@ -0,0 +1,17 @@
{
"name": "frontend-architect",
"title": "Frontend Architect",
"version": "0.1.0",
"description": "Frontend-архитектор для работы по SLM Design.",
"entry": "AGENT.md",
"references": [
"references/slm-overview.md",
"references/slm-layers.md",
"references/slm-modules.md",
"references/slm-segments.md",
"references/slm-monorepo.md",
"references/slm-react-factory.md",
"references/slm-react-factory-composition.md",
"references/slm-react-composition-provider.md"
]
}

View File

@@ -0,0 +1,36 @@
{
"references": [
{
"source": "canons/slm-design/architecture/index.md",
"target": "references/slm-overview.md"
},
{
"source": "canons/slm-design/architecture/layers.md",
"target": "references/slm-layers.md"
},
{
"source": "canons/slm-design/architecture/modules.md",
"target": "references/slm-modules.md"
},
{
"source": "canons/slm-design/architecture/segments.md",
"target": "references/slm-segments.md"
},
{
"source": "canons/slm-design/architecture/monorepo.md",
"target": "references/slm-monorepo.md"
},
{
"source": "canons/slm-design/examples/react/factory.md",
"target": "references/slm-react-factory.md"
},
{
"source": "canons/slm-design/examples/react/factory-composition.md",
"target": "references/slm-react-factory-composition.md"
},
{
"source": "canons/slm-design/examples/react/composition-provider.md",
"target": "references/slm-react-composition-provider.md"
}
]
}