feat(contracts): карточки phase-0 (#2-#9) + m3a form-инвариант всех карточек

8 карточек-контрактов phase-0: playwright-mcp, github-mcp, markdownlint, cspell,
lychee, stylelint, gitleaks, pa11y (все external, zero-hash + path"" → G4 инертен).
m3a расширен: «ВСЕ файлы contracts/ form-валидны + нет дрейфа» (loadRegistry errors
+ driftFlags пусты) — per-батч валидация прогоном vitest. m3a 3/3 GREEN.

coverage: skill:executing-plans
This commit is contained in:
Дмитрий
2026-06-08 18:38:24 +03:00
parent f1c245f8f6
commit 52cea07fee
9 changed files with 102 additions and 0 deletions
@@ -0,0 +1,12 @@
{
"skill": "cspell",
"kind": "external",
"needs": ["Markdown-текст для проверки орфографии", "пользовательский словарь проекта"],
"produces": ["отчёт о неизвестных словах"],
"constraints": ["ru/en орфография .md с пользовательским словарём", "НЕ стиль (markdownlint)", "НЕ грамматика"],
"preview-form": "none",
"defaults": ["учитывать cspell-words.txt"],
"key-decisions": ["новый валидный термин → в словарь, а не подавлять находку"],
"acceptance-criteria": ["0 неизвестных слов вне словаря"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "github-mcp",
"kind": "external",
"needs": ["ссылка на repo/issue/PR", "намерение операции"],
"produces": ["результат чтения или записи issue/PR/комментария"],
"constraints": ["внешний MCP — операции через GitHub API", "НЕ локальный git-флоу (это git/PowerShell)"],
"preview-form": "none",
"defaults": ["read-first перед мутацией"],
"key-decisions": ["scope операции: чтение или запись"],
"acceptance-criteria": ["операция отражена в GitHub и подтверждена ответом API"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "gitleaks",
"kind": "external",
"needs": ["git diff или история репозитория"],
"produces": ["находки утечек секретов (ключи/токены/пароли/DSN)"],
"constraints": ["сканирует секреты в diff/истории через lefthook pre-commit/pre-push", "НЕ SAST-уязвимости кода (Semgrep)"],
"preview-form": "none",
"defaults": ["protect --staged на pre-commit; полная история на pre-push"],
"key-decisions": ["реальный секрет vs тестовая фикстура (false-positive)"],
"acceptance-criteria": ["0 утечек секретов в diff/истории"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "lychee",
"kind": "external",
"needs": ["Markdown-файлы со ссылками"],
"produces": ["отчёт о битых URL и якорях"],
"constraints": ["внутренние + внешние ссылки и якоря .md", "НЕ стиль/орфография"],
"preview-form": "none",
"defaults": ["проверять и внутренние якоря, и внешние URL"],
"key-decisions": ["внешний временно недоступный vs реально битый — различать"],
"acceptance-criteria": ["0 битых ссылок/якорей"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "markdownlint",
"kind": "external",
"needs": ["Markdown-файлы для линта"],
"produces": ["отчёт о нарушениях стиля Markdown"],
"constraints": ["внешний CLI — стиль .md (заголовки/таблицы/пробелы/переносы)", "НЕ орфография (cspell)", "НЕ ссылки (lychee)"],
"preview-form": "none",
"defaults": ["авто-fix где возможно (--fix), кроме корневого CLAUDE.md"],
"key-decisions": ["scope: какие .md в проверке"],
"acceptance-criteria": ["0 нарушений стиля по правилам markdownlint"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "pa11y",
"kind": "external",
"needs": ["отрендеренная веб-страница / URL"],
"produces": ["отчёт о нарушениях доступности WCAG 2.1 AA"],
"constraints": ["единственный технический SoT a11y в проекте", "НЕ через Lighthouse", "НЕ визуальный смок (Playwright)"],
"preview-form": "none",
"defaults": ["проверять контраст / alt / роли / фокус-порядок по WCAG 2.1 AA"],
"key-decisions": ["какие страницы/URL в a11y-прогоне"],
"acceptance-criteria": ["0 нарушений WCAG 2.1 AA на проверяемых страницах"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "playwright-mcp",
"kind": "external",
"needs": ["URL или HTML-файл для управления", "намерение: скриншот / взаимодействие / сетевой трейс"],
"produces": ["скриншот / результат взаимодействия / снимок console+network"],
"constraints": ["внешний MCP — управляет headless-браузером", "НЕ a11y-проверка (это Pa11y)", "НЕ замена unit/e2e-тестам"],
"preview-form": "sample",
"defaults": ["read-first: снять снимок/состояние страницы до действия"],
"key-decisions": ["что проверяем: визуал, взаимодействие или сетевой трейс"],
"acceptance-criteria": ["ожидаемое состояние страницы подтверждено снимком/снапшотом"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
@@ -0,0 +1,12 @@
{
"skill": "stylelint",
"kind": "external",
"needs": ["CSS-код в .vue SFC или .css-файлах"],
"produces": ["отчёт о нарушениях стиля CSS"],
"constraints": ["CSS в .vue SFC + css-файлы", "НЕ JS/TS (ESLint)", "НЕ a11y (Pa11y)"],
"preview-form": "none",
"defaults": ["порядок свойств + именование по конфигу проекта"],
"key-decisions": ["scope: staged-файлы vs весь css"],
"acceptance-criteria": ["0 нарушений стиля CSS"],
"source": { "version": "n/a", "hash": "0000000000000000000000000000000000000000000000000000000000000000", "path": "" }
}
+6
View File
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { validateContract } from './skill-contract.mjs';
import { loadRegistry } from './skill-contract-registry.mjs';
const here = dirname(fileURLToPath(import.meta.url));
const contractsDir = join(here, '..', 'docs', 'registry', 'contracts');
@@ -16,4 +17,9 @@ describe('Машина 3-A — инварианты контрактов', () =>
const c = JSON.parse(readFileSync(join(contractsDir, 'operations-process-doc.contract.json'), 'utf8'));
expect(validateContract(c).ok).toBe(true);
});
it('ВСЕ файлы contracts/ form-валидны (нет ошибок формы/дублей/дрейфа)', () => {
const reg = loadRegistry({ dir: contractsDir });
expect(reg.errors).toEqual([]);
expect(reg.driftFlags).toEqual([]);
});
});