diff --git a/docs/registry/contracts/cspell.contract.json b/docs/registry/contracts/cspell.contract.json new file mode 100644 index 00000000..3e76e9cb --- /dev/null +++ b/docs/registry/contracts/cspell.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/github-mcp.contract.json b/docs/registry/contracts/github-mcp.contract.json new file mode 100644 index 00000000..5c08d741 --- /dev/null +++ b/docs/registry/contracts/github-mcp.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/gitleaks.contract.json b/docs/registry/contracts/gitleaks.contract.json new file mode 100644 index 00000000..b3472896 --- /dev/null +++ b/docs/registry/contracts/gitleaks.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/lychee.contract.json b/docs/registry/contracts/lychee.contract.json new file mode 100644 index 00000000..4af3a37b --- /dev/null +++ b/docs/registry/contracts/lychee.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/markdownlint.contract.json b/docs/registry/contracts/markdownlint.contract.json new file mode 100644 index 00000000..6ebc4bb4 --- /dev/null +++ b/docs/registry/contracts/markdownlint.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/pa11y.contract.json b/docs/registry/contracts/pa11y.contract.json new file mode 100644 index 00000000..5ac28dc0 --- /dev/null +++ b/docs/registry/contracts/pa11y.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/playwright-mcp.contract.json b/docs/registry/contracts/playwright-mcp.contract.json new file mode 100644 index 00000000..d9155b85 --- /dev/null +++ b/docs/registry/contracts/playwright-mcp.contract.json @@ -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": "" } +} diff --git a/docs/registry/contracts/stylelint.contract.json b/docs/registry/contracts/stylelint.contract.json new file mode 100644 index 00000000..554e8070 --- /dev/null +++ b/docs/registry/contracts/stylelint.contract.json @@ -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": "" } +} diff --git a/tools/m3a-contract-invariants.test.mjs b/tools/m3a-contract-invariants.test.mjs index e6aec0ee..eb01a3da 100644 --- a/tools/m3a-contract-invariants.test.mjs +++ b/tools/m3a-contract-invariants.test.mjs @@ -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([]); + }); });