Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 83 additions & 0 deletions .cursor/rules/n-changelog.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,86 @@ alwaysApply: true
---

У кожному **пакетному** workspace (каталог із `package.json` або `pyproject.toml`) має бути власний **`CHANGELOG.md`**. Спільного на репозиторій змісту змін **не існує** — кожен пакет веде свій. Маніфест версії: **JS/Bun/npm** — `package.json` (`version`); **Python** — `pyproject.toml` (`[project].version` або `[tool.poetry].version`).

## Дві моделі бази порівняння

Режим визначається автоматично з маніфесту.

### registry-published (npm / PyPI)

**npm:** непорожнє `name`, не `private: true`, масив `files`.

**Python:** статичні `project.name` і `project.version` у `pyproject.toml` (або Poetry-секція).

1. **Локальна `version` ≠ опублікованій** (npm / PyPI): drift поза CI → **fail** (ручний bump заборонено; навіть із change-файлом). Відкоти `version`.
2. **Версії збігаються**, але в git є **релевантні** зміни без change-файлу → fail. Для npm `"CHANGELOG.md"` має бути в `files` (публікується разом із пакетом).
3. **Реєстр недосяжний** — fail-safe pass.
4. **Немає релевантних змін** — pass.

### local-only

**npm:** `private: true` або без `files`. **Python:** без пари name+version для реєстру. База залежить від гілки:

1. На **`dev`** local-only не активний (крім незакомічених registry-published).
2. На **`main`** — diff від **`origin/main`** (попередній опублікований `main`); без remote — від `HEAD~1`. **`dev` не використовується** як база на `main`.
3. На **feature-гілці** — merge-base з **`dev`**, якщо є; інакше з **`main`** (репо без `dev`). За наявності `origin/*` беремо новішу з двох баз (локальна гілка-кандидат vs `origin/`-версія) — застарілий локальний `main`/`dev` не має перекривати вже інтегровану в origin історію, і навпаки.
4. Drift `version` від бази → **fail** (ручний bump заборонено). Зміни фіксуй change-файлом; bump зробить CI.

Якщо немає git або немає `dev`/`main`/`origin/main` — local-only пропускається.

Merge-коміт (готовий, з другим предком, або `MERGE_HEAD` під час незавершеного `git commit`) пропускається цілком — changeset документують feature-коміти, а не інтеграційний merge.

## Чеклист агента (деталі)

Основний робочий алгоритм — «перед фінальною відповіддю виконай `npx @7n/rules lint changelog`, познач результат рядком `Changelog: …`» (AGENTS.md/AGENTS.template.md); тут лише уточнення, що саме перевіряється.

**Інверсія (за замовчуванням не вимагають change-файлу):**

- зміни **лише** під `docs/` або `doc/`;
- синхронізований із `@7n/rules` інструментарій під `.cursor/` (канонічні правила й скіли) і `.claude/` (ADR-хуки) — це дзеркало tooling-пакета, а не логіка воркспейсу;
- будь-які зміни в **корені монорепо** (воркспейс `.` за наявності підпакетів) — корінь веде glue/конфіг/tooling, власного CHANGELOG не має; помітні зміни документують підпакети. Сюди потрапляють і кореневі `AGENTS.md` / `CLAUDE.md`, і bump `@7n/rules` у `devDependencies`;
- файли під **`.gitignore`**.

**Вимагають change-файл** — усі інші зміни в каталозі workspace (код, rego, правила, скіли, конфіги, тести тощо). Виняток `.cursor/` / `.claude/` **не** поширюється на джерело правил у репо `@7n/rules` — воно лежить під `npm/`, тож зміни в ньому далі вимагають change-файлу.

Ніколи не редагуй `version` і `CHANGELOG.md` вручну — навіть для hotfix; єдиний артефакт зміни — change-файл (`npx @7n/n ch [--bump <major|minor|patch>] [--section <Added|Changed|Fixed|Removed>] [--message "<…>"]`), bump/секцію CHANGELOG формує `n-rules release` у CI на `main`.

Канонічне pre-commit wiring (крок `npm-changelog` у `hk.pkl`, autofix через `N_RULES_CHANGELOG_AUTOFIX=1`) — деталь `npm-module.mdc`, тут не дублюється.

Перевірка програмна (`changelog/consistency/main.mjs`, delta-гейт присутності — `changelog/presence/main.mjs`).

## Формат CHANGELOG.md

[Keep a Changelog 1.1.0](https://keepachangelog.com/uk/1.1.0/), мова — українська, новіші версії зверху.

```md title="<ws>/CHANGELOG.md"
# Changelog

Усі помітні зміни цього пакета документуються тут.

Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).

## [1.2.3] - 2026-05-05

### Added

- ...

### Changed

- ...

### Fixed

- ...
```

Секції — підмножина `### Added`, `### Changed`, `### Fixed`, `### Removed` (одна або кілька).

Механічно `main.mjs` (`checkChangelogFormat`) перевіряє лише наявність рядка `# Changelog` (H1). Точна лексика секцій і порядок версій (новіші зверху) — конвенція, яку check **не** валідує: наявні `CHANGELOG.md` у цьому репо містять і нестандартні секції (`### BREAKING`, `### Notes`, `### TODO` тощо) з історичних причин, тож строга валідація словника секцій зробила б check несумісним із власною практикою репо. Дотримання — на розсуд автора change-файлу.

## Post-release інваріант (гарантує CI)

Перша (верхня) секція `## [version]` у `CHANGELOG.md` дорівнює полю `version` у маніфесті — але це **post-release** твердження, яке забезпечує `n-rules release` у CI, агрегуючи change-файли (bump `version` + генерація секції + git-тег `<name>@<version>`). **Локально цю рівність руками не підтримують**: у feature-флоу `version`/`CHANGELOG.md` не чіпають, тож верхня секція може відставати від майбутньої версії — це нормально. Drift `version` поза CI (vs реєстр / vs git-база) ловить цей concern (`consistency`) як заборонений ручний bump.

Інструкції щодо bump `version` і редагування `CHANGELOG.md` живуть **лише** в правилі `changelog` (деталізація моделі порівняння — `comparison-models.mdc` поряд) — джерелі істини. Інші правила (зокрема `npm-module`) їй підпорядковані щодо формату/моделі й власних інструкцій bump/CHANGELOG не дублюють; команду створення change-файлу (`npx @7n/n ch`) й заборону ручного bump вони лише повторюють як нагадування.
52 changes: 8 additions & 44 deletions .cursor/rules/n-security.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,6 @@ version: '2.1'

`npx @7n/rules fix security`

- `lint-security.yml`:

```yaml
name: Lint Security

on:
push:
branches:
- dev
- main

pull_request:
branches:
- dev
- main

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true

jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0

- uses: trufflesecurity/trufflehog@main
with:
extra_args: --results=verified,unknown
```

- `package.json`:

```json
Expand All @@ -56,14 +20,6 @@ jobs:
}
```

## Наявність кроку TruffleHog у CI workflow

Rego-пакет: `security.lint_security_yml`

Цільові файли: `.github/workflows/lint-security.yml`

Перевіряє, що серед `uses:` у workflow присутній крок `trufflesecurity/trufflehog@main`. Очікуваний перелік action-refs (не-`actions/*`) береться з `--data` через [lint-security.yml.snippet.yml](./template/lint-security.yml.snippet.yml). Універсальні кроки (`actions/*`) перевіряє `ga.workflow_common`.

## Заборона `trufflehog` у залежностях `package.json`

Rego-пакет: `security.package_json`
Expand Down Expand Up @@ -114,3 +70,11 @@ Concern `security.sample_secret` (`js/sample_secret.mjs`) сканує всі п
**Важливо:** один regex-pattern на рядок, без TOML-обгортки; коментарі починаються з `#`.

Перевірка (JS): `js/trufflehog.mjs` — впевнюється, що файл `.trufflehog-exclude` існує в корені й містить канонічні шаблони.

## Наявність кроку TruffleHog у CI workflow

Rego-пакет: `security.lint_security_yml`

Цільові файли: `.github/workflows/lint-security.yml`

Перевіряє, що серед `uses:` у workflow присутній крок `trufflesecurity/trufflehog@main`. Очікуваний перелік action-refs (не-`actions/*`) береться з `--data` через [lint-security.yml.snippet.yml](./template/lint-security.yml.snippet.yml). Універсальні кроки (`actions/*`) перевіряє `ga.workflow_common`.
8 changes: 5 additions & 3 deletions .cursor/skills/n-adr-normalize/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ git branch --show-current

**Root-assert.** Якщо `pwd` **не** збігається з виводом `git rev-parse --show-toplevel` — ти в **піддиректорії** робочого дерева (worktree-шляхи нижче відносні до кореня репо). Спершу перейди в корінь: `cd <toplevel>` (literal-шлях із виводу), і лише тоді продовжуй preflight. Не створюй worktree з піддиректорії — `cd .worktrees/<…>` звідти впаде.

Якщо `git rev-parse --show-toplevel` показав, що ти **не** в `.worktrees/`, візьми вивід `git branch --show-current` як `<current-branch>` і виконай **literal-команди без shell expansion** (без command substitution, variable expansion чи backticks). Наприклад, якщо поточна гілка `feature/x`:
**Вже ізольований — нічого не створюй.** Якщо `git rev-parse --show-toplevel` містить сегмент `.worktrees/<…>` (репо-конвенція) **або** `.claude/worktrees/<…>` (worktree харнесу Claude Code — туди `npx @7n/mt worktree create` класти заборонено, `n-worktree.mdc`) — ти вже виконуєшся в окремому git-worktree. Preflight пройдено: нічого не створюй, нікого не питай про назву гілки — переходь одразу до Кроку 0.1.

Інакше, якщо toplevel не містить жодного з цих сегментів, візьми вивід `git branch --show-current` як `<current-branch>` і виконай **literal-команди без shell expansion** (без command substitution, variable expansion чи backticks). Наприклад, якщо поточна гілка `feature/x`:

```bash
npx @7n/mt worktree create "feature/x-adr-normal" "n-adr-normal: worktree-only skill"
Expand All @@ -29,10 +31,10 @@ cd ".worktrees/feature-x-adr-normal"

Тобто branch-argument лишає slash як у git-гілці, а шлях для `cd` бере sanitized форму: slash → `-`.

**Крок 0.1 — bootstrap у новому дереві (після `cd`).** Дерево щойно створене й **без** `node_modules`. Постав залежності локально — тоді `npx @7n/rules <cmd>` бере локальну копію без походу в реєстр:
**Крок 0.1 — bootstrap (якщо в дереві ще нема `node_modules`).** Свіжостворений worktree (Крок 0) точно без `node_modules`; вже ізольований harness-worktree може мати їх або ні — постав локально, тоді `npx @7n/rules <cmd>` бере локальну копію без походу в реєстр:

```bash
bun install
test -d node_modules || bun install
```
<!-- n-rules:worktree:end -->

Expand Down
8 changes: 5 additions & 3 deletions .cursor/skills/n-lint/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ git branch --show-current

**Root-assert.** Якщо `pwd` **не** збігається з виводом `git rev-parse --show-toplevel` — ти в **піддиректорії** робочого дерева (worktree-шляхи нижче відносні до кореня репо). Спершу перейди в корінь: `cd <toplevel>` (literal-шлях із виводу), і лише тоді продовжуй preflight. Не створюй worktree з піддиректорії — `cd .worktrees/<…>` звідти впаде.

Якщо `git rev-parse --show-toplevel` показав, що ти **не** в `.worktrees/`, візьми вивід `git branch --show-current` як `<current-branch>` і виконай **literal-команди без shell expansion** (без command substitution, variable expansion чи backticks). Наприклад, якщо поточна гілка `feature/x`:
**Вже ізольований — нічого не створюй.** Якщо `git rev-parse --show-toplevel` містить сегмент `.worktrees/<…>` (репо-конвенція) **або** `.claude/worktrees/<…>` (worktree харнесу Claude Code — туди `npx @7n/mt worktree create` класти заборонено, `n-worktree.mdc`) — ти вже виконуєшся в окремому git-worktree. Preflight пройдено: нічого не створюй, нікого не питай про назву гілки — переходь одразу до Кроку 0.1.

Інакше, якщо toplevel не містить жодного з цих сегментів, візьми вивід `git branch --show-current` як `<current-branch>` і виконай **literal-команди без shell expansion** (без command substitution, variable expansion чи backticks). Наприклад, якщо поточна гілка `feature/x`:

```bash
npx @7n/mt worktree create "feature/x-lint" "n-lint: worktree-only skill"
Expand All @@ -28,10 +30,10 @@ cd ".worktrees/feature-x-lint"

Тобто branch-argument лишає slash як у git-гілці, а шлях для `cd` бере sanitized форму: slash → `-`.

**Крок 0.1 — bootstrap у новому дереві (після `cd`).** Дерево щойно створене й **без** `node_modules`. Постав залежності локально — тоді `npx @7n/rules <cmd>` бере локальну копію без походу в реєстр:
**Крок 0.1 — bootstrap (якщо в дереві ще нема `node_modules`).** Свіжостворений worktree (Крок 0) точно без `node_modules`; вже ізольований harness-worktree може мати їх або ні — постав локально, тоді `npx @7n/rules <cmd>` бере локальну копію без походу в реєстр:

```bash
bun install
test -d node_modules || bun install
```
<!-- n-rules:worktree:end -->

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ dist/
.codex/hooks/.normalize.lock
.claude/worktrees/
.worktrees/
# @7n/rules (adr) — локальні артефакти Stop-hook, не коміти
.claude/scheduled_tasks.lock
8 changes: 4 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"CHANGELOG.md"
],
"devDependencies": {
"@7n/rules": "^1.43.1",
"@7n/rules-ci-github": "^1.9.0",
"@7n/rules": "^1.44.1",
"@7n/rules-ci-github": "^1.9.2",
"@nitra/cspell-dict": "^2.2.2"
},
"engines": {
Expand Down
Loading