Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
af4fa8b
docs(input): восстановлены стори группы Input
fabulous-unicorn Oct 27, 2026
861aef4
docs(input-switch): восстановлена стори InputSwitch
fabulous-unicorn Oct 27, 2026
bddbc93
docs(typography): добавлена стори Overview для типографии
fabulous-unicorn Oct 27, 2026
20389ba
feat(storybook): добавлены метки состояния компонентов
fabulous-unicorn Oct 27, 2026
284ff68
chore(storybook): настроен порядок разделов и сторис
fabulous-unicorn Oct 27, 2026
1400dee
docs(storybook): объединены сторис компонентов форм
fabulous-unicorn Oct 27, 2026
b4a5eb5
docs(storybook): сгруппированы меню и вкладки
fabulous-unicorn Oct 27, 2026
9e654bc
docs(dialog): объединены варианты диалога в одной стори
fabulous-unicorn Oct 27, 2026
0fec30e
docs(accordion): упрощена интерактивная стори аккордеона
fabulous-unicorn Oct 27, 2026
ca5241a
docs(storybook): переименованы группы полей ввода и рейтинга
fabulous-unicorn Oct 27, 2026
ff2ed81
docs(storybook): перенесены стори устаревших компонентов в общие группы
fabulous-unicorn Oct 27, 2026
ab24c51
fix(storybook): исправлен регистр метки design pending
fabulous-unicorn Oct 27, 2026
15ac6b1
fix(storybook): исправлена очистка поля LegacyInputMask
fabulous-unicorn Oct 27, 2026
435b2fa
feat(storybook): добавлена метка исключения из версии 2.0
fabulous-unicorn Oct 27, 2026
163b96d
docs(storybook): отмечены устаревшие компоненты меню
fabulous-unicorn Oct 27, 2026
1854e61
docs(storybook): отмечены компоненты для проверки дизайна
fabulous-unicorn Oct 27, 2026
7494c34
refactor(storybook): упрощён экспорт стори Dialog
fabulous-unicorn Oct 28, 2026
27127ce
chore(skills): обновлены проектные навыки и проверки
fabulous-unicorn Oct 28, 2026
0cd982f
chore(skills): расширены навыки API, Figma и Storybook
fabulous-unicorn Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions .agents/scripts/check-skills.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import assert from 'node:assert/strict'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { dirname, join, relative, resolve, sep } from 'node:path'
import process from 'node:process'
import { pathToFileURL } from 'node:url'

import { parse } from 'yaml'

const read = (file) => readFileSync(file, 'utf8')

const readMapping = (text) => {
const value = parse(text)
assert(
value && typeof value === 'object' && !Array.isArray(value),
'expected YAML mapping'
)

return value
}

const checkFrontmatter = (file, name) => {
const match = read(file).match(
/^---\r?\n(?<metadata>[\s\S]*?)\r?\n---(?:\r?\n|$)/u
)
assert(match, 'missing YAML frontmatter')
const metadata = readMapping(match.groups.metadata)
assert(
/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(name) && name.length <= 64,
'invalid skill name'
)
assert.equal(metadata.name, name, 'name must match the skill directory')
assert(
typeof metadata.description === 'string' && metadata.description.trim(),
'missing description'
)
}

const checkManifest = (file, name) => {
const metadata = readMapping(read(file))

if (metadata.interface === undefined) return

const ui = metadata.interface
assert(
ui && typeof ui === 'object' && !Array.isArray(ui),
'invalid interface mapping'
)

for (const [key, value] of Object.entries(ui)) {
assert(
typeof value === 'string' && value.trim(),
`invalid interface.${key}`
)
}

if (ui.short_description !== undefined) {
assert(
ui.short_description.length >= 25 && ui.short_description.length <= 64,
'short_description must be 25–64 characters'
)
}

if (ui.default_prompt !== undefined) {
assert(
ui.default_prompt.includes(`$${name}`),
'default_prompt must mention its skill'
)
}
}

const checkLinks = (file, skillRoot) => {
const text = read(file).replace(
/^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[ \t]*$/gmu,
''
)

for (const {
groups: { target },
} of text.matchAll(/\[[^\]]*\]\((?<target>[^)]+)\)/gu)) {
const path = target.trim().replace(/^<|>$/gu, '')

if (!/^[a-z][a-z0-9+.-]*:/iu.test(path) && !path.startsWith('#')) {
assert(
existsSync(
resolve(dirname(file), decodeURIComponent(path.split('#')[0]))
),
`missing link target: ${target}`
)
}
}

for (const {
groups: { target },
} of text.matchAll(/`(?<target>(?:references|scripts|assets)\/[^`\s]+)`/gu)) {
assert(existsSync(join(skillRoot, target)), `missing resource: ${target}`)
}
}

export const checkSkills = (repoRoot) => {
const errors = []
const check = (file, action) => {
try {
action()
} catch (error) {
errors.push(`${relative(repoRoot, file)}: ${error.message}`)
}
}
const skillsRoot = join(repoRoot, '.agents/skills')
const names = readdirSync(skillsRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
.map((entry) => entry.name)

for (const name of names) {
const root = join(skillsRoot, name)
const entry = join(root, 'SKILL.md')
check(entry, () => checkFrontmatter(entry, name))
const manifest = join(root, 'agents/openai.yaml')

if (existsSync(manifest))
check(manifest, () => checkManifest(manifest, name))

const references = join(root, 'references')
const documents = existsSync(references)
? readdirSync(references, { recursive: true })
.filter(
(path) =>
path.endsWith('.md') && !path.split(sep).includes('vendor')
)
.map((path) => join(references, path))
: []

for (const file of [entry, ...documents])
check(file, () => checkLinks(file, root))
}

const agents = join(repoRoot, 'AGENTS.md')
check(agents, () => {
for (const {
groups: { name },
} of read(agents).matchAll(/\$(?<name>[a-z][a-z0-9-]*)/gu)) {
assert(names.includes(name), `unknown project skill: ${name}`)
}
})

return errors
}

if (import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
const errors = checkSkills(resolve(import.meta.dirname, '../..'))

if (errors.length) {
console.error(errors.join('\n'))
process.exitCode = 1
} else {
console.log(
'Project skill metadata, links, and AGENTS.md routing are valid.'
)
}
}
88 changes: 88 additions & 0 deletions .agents/scripts/test-check-skills.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import assert from 'node:assert/strict'
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { test } from 'node:test'

import { checkSkills } from './check-skills.mjs'

const fixture = (context) => {
const root = mkdtempSync(join(tmpdir(), 'ui-kit-skills-'))
context.after(() => rmSync(root, { recursive: true, force: true }))
const write = (path, text) => {
const file = join(root, path)
mkdirSync(dirname(file), { recursive: true })
writeFileSync(file, text)
}
write('AGENTS.md', 'Use $sample.\n')
write(
'.agents/skills/sample/SKILL.md',
'---\nname: sample\ndescription: Example skill\n---\n[Guide](references/guide.md)\n'
)
write('.agents/skills/sample/references/guide.md', '# Guide\n')
write(
'.agents/skills/sample/agents/openai.yaml',
'interface:\n short_description: "Describe a valid example skill"\n default_prompt: "Use $sample for this task."\n'
)

return { root, write }
}

test('accepts valid metadata, linked references, and project routing', (context) => {
const { root, write } = fixture(context)
write('.agents/skills/sample/vendor/copied/SKILL.md', 'not active metadata')
assert.deepEqual(checkSkills(root), [])
})

test('reports malformed YAML in entrypoints and manifests', (context) => {
const { root, write } = fixture(context)
write(
'.agents/skills/sample/SKILL.md',
'---\nname: sample\nname: duplicate\n---\n'
)
write('.agents/skills/sample/agents/openai.yaml', 'interface: [\n')
const errors = checkSkills(root)
assert.equal(errors.length, 2)
assert(
errors.some((error) => error.startsWith('.agents/skills/sample/SKILL.md:'))
)
assert(
errors.some((error) =>
error.startsWith('.agents/skills/sample/agents/openai.yaml:')
)
)
})

test('reports missing links and literal resources while ignoring examples and remote URLs', (context) => {
const { root, write } = fixture(context)
write(
'.agents/skills/sample/references/guide.md',
'[Remote](https://example.com)\n[Section](#section)\n```md\n[Example](placeholder.md)\n```\n[Missing](missing.md)\n'
)
let errors = checkSkills(root)
assert.equal(errors.length, 1)
assert.match(errors[0], /missing\.md/u)
write(
'.agents/skills/sample/references/guide.md',
'Run `scripts/missing.mjs`.\n'
)
errors = checkSkills(root)
assert.equal(errors.length, 1)
assert.match(errors[0], /scripts\/missing\.mjs/u)
})

test('reports a mismatched skill name, invalid prompt, and unresolved AGENTS routing', (context) => {
const { root, write } = fixture(context)
write(
'.agents/skills/sample/SKILL.md',
'---\nname: another\ndescription: Example skill\n---\n'
)
write(
'.agents/skills/sample/agents/openai.yaml',
'interface:\n default_prompt: "Use $another."\n'
)
write('AGENTS.md', 'Use $missing.\n')
const errors = checkSkills(root)
assert.equal(errors.length, 3)
assert(errors.some((error) => error.startsWith('AGENTS.md:')))
})
56 changes: 56 additions & 0 deletions .agents/skills/ui-kit-component-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: ui-kit-component-docs
description: >
Write or shorten Russian UI Kit component Markdown and public API JSDoc,
excluding task descriptions, migration reports, and release notes.
---

# Документация компонентов Prime UI Kit

Сверь публичные props, экспорты, defaults, примеры и поведение с исходниками,
тестами и Storybook. За образец бери актуальные Tag, Badge, Button или Input.
Пример другой ветки читай через `git show`, не переключая рабочую область.

## Markdown

Создавай `<Component>.md` рядом с компонентом. Используй только нужные разделы:

1. Название и одно предложение о назначении.
2. `Использование` — минимальный пример с публичным импортом пакета.
3. `Props` — сначала назначение собственного prop, затем существенные варианты,
defaults (включая встроенные иконки) и ограничения сочетаний. Добавь
относительную ссылку на публичный тип, для вложенного компонента — на его
тип.
4. `Доступность` — как получается доступное имя, роли и ответственность
потребителя. Если разработчику нужно задать имя или учесть другой нюанс,
добавь короткую рекомендацию с условием её применения. При необходимости
поясни интерактивного родителя и масштаб шрифта.
5. `Макет` — точная известная ссылка на компонент в Figma.

Стандартные RN props не перечисляй, особое поведение объясняй по месту. Сначала
опиши назначение prop, затем особенности a11y. `accessibilityLabel` описывай в
доступности: различай автоматическое озвучивание компонента и добавление его
смысла потребителем в доступное имя родителя.

## JSDoc И Краткость

Для изменяемого публичного компонента и его собственных props опиши назначение,
смысл callback, controlled-поведение, defaults и существенные ограничения.
Рекомендацию для потребителя повтори в JSDoc соответствующего prop или
компонента, чтобы она была видна при использовании API в редакторе. Добавляй
точную известную Figma-ссылку через `@see`. Для deprecated API укажи замену без
неподтверждённого срока удаления.

Пиши кратко по-русски, без повторов, внутренних функций, токенов, тестов и
хронологии. Сохраняй подтверждённые ограничения: сокращение текста, условия
label/caption, обязательную иконку и доступное имя для iconOnly. Не обещай
непроверенные платформенные возможности.

Совместимость и миграцию не включай в документацию компонента. Если запрошены,
выдай отдельно в чате. README и ссылки из Storybook меняй только по запросу,
сохраняя существующий формат.

## Проверка

Проверь относительные ссылки и точный Figma-узел. Запусти целевой Prettier и
`git diff --check`; остальные проверки и отчёт — по `AGENTS.md`.
3 changes: 3 additions & 0 deletions .agents/skills/ui-kit-component-docs/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: 'UI Kit Component Docs'
short_description: 'Write Russian component Markdown and API JSDoc'
Loading
Loading