chore: добавить сборку frontend-агента
- добавлен агент frontend-architect с манифестом и reference-картой - добавлен скрипт сборки архива агента в public/agents - добавлена сборка агентов в CI и Docker-сборку - исключены generated-директории public/agents и public/template-sync-strategy - удалены сгенерированные файлы Template Sync Strategy из git
This commit is contained in:
189
agents/frontend-architect/scripts/build.ts
Normal file
189
agents/frontend-architect/scripts/build.ts
Normal 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();
|
||||
88
agents/frontend-architect/source/AGENT.md
Normal file
88
agents/frontend-architect/source/AGENT.md
Normal 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-совместимый вариант. Если правил недостаточно для решения, задай один уточняющий вопрос.
|
||||
17
agents/frontend-architect/source/manifest.json
Normal file
17
agents/frontend-architect/source/manifest.json
Normal 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"
|
||||
]
|
||||
}
|
||||
36
agents/frontend-architect/source/references.json
Normal file
36
agents/frontend-architect/source/references.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user