Skip to content
This repository was archived by the owner on Sep 11, 2026. It is now read-only.
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
10 changes: 7 additions & 3 deletions bun.lock

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

5 changes: 5 additions & 0 deletions npm/.changes/260720-0942.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: minor
section: Added
---
tauri: додано концерн gitignore_target — перевірка й автофікс ignore-запису ws/src-tauri/target/ у корінному .gitignore (typo-інцидент nitra/task)
8 changes: 8 additions & 0 deletions npm/rules/tauri/gitignore_target/concern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://unpkg.com/@7n/rules/schemas/concern.json",
"fixability": "config",
"lint": {
"scope": "full",
"glob": [".gitignore", "**/src-tauri/Cargo.toml"]
}
}
31 changes: 31 additions & 0 deletions npm/rules/tauri/gitignore_target/docs/fix-gitignore_target.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
type: JS Module
title: fix-gitignore_target.mjs
resource: npm/rules/tauri/gitignore_target/fix-gitignore_target.mjs
docgen:
crc: 0ceef709
model: openai-codex/gpt-5.4-mini
score: 100
issues: judge:inaccurate:0.98
judgeModel: openai-codex/gpt-5.4-mini
---

## Огляд

T0-autofix для `tauri/gitignore_target`: дописує в корінний `.gitignore` відсутні ignore-записи `<ws>/src-tauri/target/` у секцію з міткою `# Tauri — Rust build artifacts (tauri.mdc)` (константа `GITIGNORE_TARGET_HEADER`). `insertMissingTargetEntries` виконує сам текстовий splice; `patterns` — масив T0-визначень (`id`/`test`/`apply`) центрального fix-pipeline, що зчитує відсутні записи з `violation.data.missing` і застосовує `insertMissingTargetEntries` до файлів через `applyToFiles`.

## Поведінка

- `GITIGNORE_TARGET_HEADER` — мітка секції в корінному `.gitignore`, куди групуються записи для Tauri build artifacts.
- `insertMissingTargetEntries` — дописує відсутні ignore-записи для `<ws>/src-tauri/target/` у `.gitignore`: або в наявну секцію з `GITIGNORE_TARGET_HEADER` (поруч з іншими entries), або в новий блок у кінець файла; якщо нічого додавати не треба (`missingEntries` порожній), повертає `null` і вміст не змінюється.
- `patterns` — один T0-патерн (`tauri-gitignore-target-insert`): спрацьовує на violations з `data.kind === MISSING_GITIGNORE_TARGET_ENTRIES` і застосовує `insertMissingTargetEntries` до кожного цільового `.gitignore`.

## Публічний API

- GITIGNORE_TARGET_HEADER — заголовок секції в корінному `.gitignore` для Tauri build-артефактів.
- insertMissingTargetEntries — додає відсутні `"<ws>/src-tauri/target/"` у відповідну секцію `.gitignore` або створює новий блок наприкінці файла.
- patterns — T0-визначення autofix-у для detector-а `tauri/gitignore_target`, що підключається до центрального fix-pipeline.

## Гарантії поведінки

- (специфічних машинно-виведених гарантій немає)
10 changes: 10 additions & 0 deletions npm/rules/tauri/gitignore_target/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
type: Directory Index
title: npm/rules/tauri/gitignore_target
resource: npm/rules/tauri/gitignore_target/
---

| Файл | Тип |
| --------------------------------------------------- | --------- |
| [fix-gitignore_target.mjs](fix-gitignore_target.md) | JS Module |
| [main.mjs](main.md) | JS Module |
37 changes: 37 additions & 0 deletions npm/rules/tauri/gitignore_target/docs/main.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
type: JS Module
title: main.mjs
resource: npm/rules/tauri/gitignore_target/main.mjs
docgen:
crc: 83aa81ee
model: openai-codex/gpt-5.5
tier: cloud-avg
score: 85
issues: internal-name:findSrcTauriDirs,anchor-miss:(tauri.mdc),judge:inaccurate:0.98
judgeModel: openai-codex/gpt-5.4-mini
---

## Огляд

Файл read-only обходить усі `<ws>/src-tauri/` у монорепо спільним механізмом із `tauri/cargo_mutants_config` (`findSrcTauriDirs`), без дублювання обходу. Для кожного знайденого workspace корінний `ROOT_GITIGNORE = ".gitignore"` повинен містити точний запис `<ws>/src-tauri/target/`, звірений по точному рядку — typo на кшталт `owner/target/` (реальний інцидент у `nitra/task`) не рахується присутністю потрібного запису.

## Поведінка

- `MISSING_GITIGNORE_TARGET_ENTRIES` — reason `"missing-gitignore-target-entries"` для порушення, коли в корінному `.gitignore` бракує записів для Tauri build-артефактів.
- `ROOT_GITIGNORE` — назва корінного файлу `".gitignore"`, у якому мають бути всі очікувані ignore-записи.
- `expectedTargetEntry` — формує точний ignore-запис для `target/` конкретного `src-tauri/` відносно кореня монорепо.
- `findMissingEntries` — визначає, яких очікуваних ignore-записів немає у `.gitignore`, за точним збігом рядка (після trim).
- `lint` — read-only перевіряє всі знайдені `src-tauri/` у монорепо й повідомляє про відсутні ignore-записи одним violation на весь `.gitignore`.

## Публічний API

- MISSING_GITIGNORE_TARGET_ENTRIES — reason `missing-gitignore-target-entries` для випадків, коли в кореневому `.gitignore` немає записів для `src-tauri/target/`.
- ROOT_GITIGNORE — файл `.gitignore` у корені монорепо, який є єдиним місцем для цих ignore-записів.
- expectedTargetEntry — формує потрібний запис для ігнорування build-артефактів окремого workspace з `src-tauri/`.
- findMissingEntries — визначає, яких очікуваних записів бракує в кореневому `.gitignore`; збіг рахується лише для точного рядка після trim, тому `target/` або `owner/target/` не замінюють потрібний запис.
- lint — повідомляє про workspace з Tauri, для яких у кореневому `.gitignore` бракує `src-tauri/target/`.

## Гарантії поведінки

- Read-only: не виконує операцій запису (ФС/БД).
- Свідомо пропускає шляхи: `.git`.
78 changes: 78 additions & 0 deletions npm/rules/tauri/gitignore_target/fix-gitignore_target.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/** @see ./docs/fix-gitignore_target.md */

/**
* T0-autofix для `tauri/gitignore_target` — детерміновано дописує в корінний
* `.gitignore` відсутні ignore-записи `<ws>/src-tauri/target/` (з violation.data.missing,
* а не з повторного сканування монорепо — детектор уже визначив, яких саме
* записів бракує). Текстовий splice (як `tauri/linux_deps`): зберігає коментарі
* й формат, мінімальний diff. Ідемпотентно: повторний прогін на вже
* виправленому файлі нічого не змінює (`findMissingEntries` у main.mjs
* повторно перевіряє стан файла).
*/
import { applyToFiles } from '../../../scripts/utils/apply-to-files.mjs'

import { MISSING_GITIGNORE_TARGET_ENTRIES } from './main.mjs'

/** Заголовок-коментар секції Tauri build-артефактів у корінному `.gitignore`. */
export const GITIGNORE_TARGET_HEADER = '# Tauri — Rust build artifacts (tauri.mdc)'

/**
* Знаходить кінець контурного блоку entries, що йде одразу за заголовком
* (перший порожній рядок, наступний коментар або кінець файла).
* @param {string[]} lines рядки `.gitignore`
* @param {number} headerIdx індекс рядка заголовка
* @returns {number} індекс, куди вставляти нові entries
*/
function findBlockEnd(lines, headerIdx) {
let i = headerIdx + 1
while (i < lines.length && lines[i].trim() !== '' && !lines[i].trim().startsWith('#')) i++
return i
}

/**
* Дописує відсутні `<ws>/src-tauri/target/` entries: якщо секція `GITIGNORE_TARGET_HEADER`
* вже є — вставляє в кінець її блоку (поруч з наявними entries); інакше додає
* новий блок (заголовок + entries) у кінець файла.
* @param {string} content вміст `.gitignore`
* @param {string[]} missingEntries відсутні ignore-рядки (`<ws>/src-tauri/target/`)
* @returns {string|null} новий вміст або null, якщо нічого не змінилось
*/
export function insertMissingTargetEntries(content, missingEntries) {
if (missingEntries.length === 0) return null

const lines = content.split('\n')
const headerIdx = lines.findIndex(l => l.trim() === GITIGNORE_TARGET_HEADER)

if (headerIdx !== -1) {
const blockEnd = findBlockEnd(lines, headerIdx)
lines.splice(blockEnd, 0, ...missingEntries)
return lines.join('\n')
}

const trailingBlank = lines.length > 0 && lines.at(-1) === ''
const body = trailingBlank ? lines.slice(0, -1) : lines
const needsBlankSep = body.length > 0 && body.at(-1).trim() !== ''

const next = [...body]
if (needsBlankSep) next.push('')
next.push(GITIGNORE_TARGET_HEADER, ...missingEntries, '')
return next.join('\n')
}

/** @type {import('../../../scripts/lib/lint-surface/types.mjs').T0Pattern[]} */
export const patterns = [
{
id: 'tauri-gitignore-target-insert',
test: violations => violations.some(v => v.data?.kind === MISSING_GITIGNORE_TARGET_ENTRIES && v.file),
apply: (violations, ctx) => {
const targets = violations.filter(v => v.data?.kind === MISSING_GITIGNORE_TARGET_ENTRIES && v.file)
const touchedFiles = applyToFiles(targets, ctx, rel => content => {
const v = targets.find(x => x.file === rel)
return insertMissingTargetEntries(content, v?.data?.missing ?? [])
})
return touchedFiles.length > 0
? { touchedFiles, message: `Tauri build-артефакти → .gitignore (${touchedFiles.length} file(s))` }
: { touchedFiles: [] }
}
}
]
24 changes: 24 additions & 0 deletions npm/rules/tauri/gitignore_target/gitignore_target.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
## `.gitignore`: ignore build-артефактів `<ws>/src-tauri/target/`

Кожен `<ws>/src-tauri/` (Rust-крейт Tauri) компілюється в `<ws>/src-tauri/target/` — build-артефакти Cargo (сотні MB, десятки тисяч файлів). Корінний `.gitignore` монорепо повинен містити точний ignore-запис для кожного такого воркспейсу:

```gitignore
# Tauri — Rust build artifacts (tauri.mdc)
<ws>/src-tauri/target/
```

### Чому точний шлях, а не голий `target/`

Реальний інцидент: у `nitra/task` корінний `.gitignore` мав `owner/target/` замість `owner/src-tauri/target/` — воркспейс скопійований з іншого проєкту без заміни шляху (typo). `target/` там ігнорувався не там, де компілятор його реально створює, тож ~600MB / ~28k файлів build-артефактів не потрапляли під жоден ignore-патерн і мало не потрапили в коміт через `git add -A`.

Тому перевірка звіряє **точний рядок** `<ws>/src-tauri/target/` для кожного знайденого `src-tauri/`, а не факт наявності підрядка `target/` десь у файлі — голий `target/` (чи вже наявний, але помилковий `owner/target/`) не закриває violation.

### Область дії

- Обхід усіх `<ws>/src-tauri/` — спільний з `tauri/cargo_mutants_config` (`findSrcTauriDirs`, без дублювання обходу монорепо).
- Ціль фіксу — **один** корінний `.gitignore` монорепо (не по workspace).
- Детектор лише **додає** відсутнє: сторонні/застарілі ignore-записи (на кшталт помилкового `owner/target/` з інциденту вище) не видаляються — вони можуть бути навмисними чи потребувати ручного review.

### Автофікс (T0)

Дописує відсутні записи в кінець секції `# Tauri — Rust build artifacts (tauri.mdc)`: якщо секція вже є — поруч із наявними entries (мінімальний diff, без переписування файла); якщо нема — створює новий блок у кінці `.gitignore`. Ідемпотентно: повторний прогін на вже виправленому файлі нічого не змінює.
84 changes: 84 additions & 0 deletions npm/rules/tauri/gitignore_target/main.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @see ./docs/main.md
*
* Read-only detector: для кожного знайденого `<ws>/src-tauri/` у монорепо
* корінний `.gitignore` повинен містити точний ignore-запис
* `<ws>/src-tauri/target/` (tauri.mdc).
*
* Реальний інцидент: у `nitra/task` `.gitignore` мав `owner/target/` замість
* `owner/src-tauri/target/` (typo — воркспейс скопійований без заміни шляху),
* тому build-артефакти (~600MB, ~28k файлів) не ігнорувались і мало не
* потрапили в коміт через `git add -A`. Тому перевірка — точний match рядка
* повного шляху, не пошук голого `target/` десь у файлі: голий `target/`
* (чи `owner/target/`) дав би false negative саме на цьому кейсі.
*
* Обхід `<ws>/src-tauri/` — спільний з `tauri/cargo_mutants_config`
* (`findSrcTauriDirs`), без дублювання.
*/
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { join, relative } from 'node:path'

import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
import { findSrcTauriDirs } from '../cargo_mutants_config/main.mjs'

/** Стабільний reason: у корінному `.gitignore` бракує ignore-запису(ів) для `src-tauri/target/`. */
export const MISSING_GITIGNORE_TARGET_ENTRIES = 'missing-gitignore-target-entries'

/** Корінний `.gitignore` — один на монорепо, не по workspace. */
export const ROOT_GITIGNORE = '.gitignore'

/**
* Очікуваний ignore-запис для build-артефактів одного `<ws>/src-tauri/`.
* @param {string} cwd корінь монорепо
* @param {string} srcTauriDir абсолютний шлях до `src-tauri/`
* @returns {string} очікуваний рядок у корінному `.gitignore` (`<ws>/src-tauri/target/`)
*/
export function expectedTargetEntry(cwd, srcTauriDir) {
return `${relative(cwd, srcTauriDir)}/target/`
}

/**
* Знаходить очікувані entries, яких бракує у вмісті кореневого `.gitignore`.
* Точний match рядка (після trim) — голий `target/` чи typo на кшталт
* `owner/target/` не рахуються присутністю очікуваного запису.
* @param {string} content вміст `.gitignore`
* @param {string[]} expectedEntries очікувані ignore-рядки
* @returns {string[]} відсутні entries (зі збереженням порядку expectedEntries)
*/
export function findMissingEntries(content, expectedEntries) {
const present = new Set(content.split('\n').map(l => l.trim()))
return expectedEntries.filter(e => !present.has(e))
}

/**
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінт-прогону
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат перевірки
*/
export async function lint(ctx) {
const cwd = ctx.cwd
const reporter = createViolationReporter(ctx)

const srcTauriDirs = await findSrcTauriDirs(cwd)
if (srcTauriDirs.length === 0) return reporter.result()

const expectedEntries = srcTauriDirs.map(dir => expectedTargetEntry(cwd, dir))
const abs = join(cwd, ROOT_GITIGNORE)
const content = existsSync(abs) ? await readFile(abs, 'utf8') : ''

const missing = findMissingEntries(content, expectedEntries)
if (missing.length === 0) {
reporter.pass(`${ROOT_GITIGNORE}: build-артефакти всіх src-tauri/ ігноруються`)
return reporter.result()
}

reporter.fail(
`${ROOT_GITIGNORE}: бракує ignore-запису(ів) для Tauri build-артефактів [${missing.join(', ')}] — build-артефакти можуть потрапити в коміт (tauri.mdc)`,
{
reason: MISSING_GITIGNORE_TARGET_ENTRIES,
file: ROOT_GITIGNORE,
data: { kind: MISSING_GITIGNORE_TARGET_ENTRIES, missing }
}
)
return reporter.result()
}
Loading
Loading