@nitra/cursor 1.13.25 → 1.13.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-template/hooks/normalize-decisions.sh +1 -1
- package/CHANGELOG.md +32 -0
- package/package.json +1 -1
- package/rules/adr/adr.mdc +2 -2
- package/rules/hasura/fix/internal_urls/check.mjs +3 -2
- package/rules/hasura/hasura.mdc +3 -3
- package/rules/hasura/policy/svc_hl/svc_hl.rego +37 -15
- package/rules/hasura/policy/svc_hl/target.json +3 -1
- package/rules/js-lint/js-lint.mdc +9 -1
- package/rules/js-run/js-run.mdc +6 -2
- package/rules/security/fix/trufflehog/check.mjs +45 -0
- package/rules/security/fix/trufflehog/template/.trufflehog-exclude.snippet.txt +6 -0
- package/rules/security/policy/lint_security_yml/lint_security_yml.rego +33 -0
- package/rules/security/policy/lint_security_yml/target.json +8 -0
- package/rules/security/policy/lint_security_yml/template/lint-security.yml.snippet.yml +31 -0
- package/rules/security/policy/package_json/template/package.json.deny.json +2 -2
- package/rules/security/policy/package_json/template/package.json.snippet.json +1 -1
- package/rules/security/security.mdc +17 -38
- package/rules/text/text.mdc +12 -2
- package/scripts/utils/template.mjs +3 -2
- package/skills/adr-normalize/SKILL.md +2 -2
- package/rules/security/fix/gitleaks/check.mjs +0 -25
- package/rules/security/fix/gitleaks/template/.gitleaks.toml.snippet.toml +0 -12
- package/rules/security/policy/gitleaks/gitleaks.rego +0 -17
- package/rules/security/policy/gitleaks/target.json +0 -8
|
@@ -78,7 +78,7 @@ if [ -f "$STATE_FILE" ]; then
|
|
|
78
78
|
fi
|
|
79
79
|
|
|
80
80
|
THRESHOLD="${ADR_NORMALIZE_THRESHOLD:-30}"
|
|
81
|
-
BATCH_SIZE="${ADR_NORMALIZE_BATCH:-
|
|
81
|
+
BATCH_SIZE="${ADR_NORMALIZE_BATCH:-10}"
|
|
82
82
|
DRY_RUN="${ADR_NORMALIZE_DRY:-0}"
|
|
83
83
|
|
|
84
84
|
# Detects whether a markdown file is a draft: has YAML frontmatter with `session:` field.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.13.28] - 2026-05-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `scripts/utils/template.mjs` (`stripJsonComments`): враховує контекст рядкових літералів. Раніше regex `\/\*[\s\S]*?\*\/` без розрізнення string-літералів агресивно вирізав блоки між `/*` і `*/`, які зустрічаються в glob-патернах JSON-значень (напр. `**/node_modules/**`, `**/k8s/**/*.yaml`), і канонічний `.cspell.json.snippet.json` чи `.oxfmtrc.json.snippet.json` після стрипу стягувався в один склеєний рядок замість 7-елементного масиву. Новий стриппер пропускає вміст `"..."` (з підтримкою backslash-escape) без змін і вирізає лише реальні JSONC-коментарі.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `hasura` rule (`hasura.svc_hl`): іменування Service узгоджено з `k8s.svc_hl_yaml` — headless (`spec.clusterIP: None`) має суфікс `-h-hl` (напр. `db-h` → `db-h-hl`), clusterIP у `svc.yaml` — `-h`. Target розширено на `hasura/k8s/base/svc.yaml` і `svc-hl.yaml`; додано `svc_hl_test.rego`. `hasura.mdc` і `fix/internal_urls` оновлено під headless DNS (`contract-h-hl`). Bump `hasura.mdc` `1.1` → `1.2`.
|
|
16
|
+
|
|
17
|
+
## [1.13.27] - 2026-05-18
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `text`, `js-lint`, `js-run` rules: додано markdown-посилання на template-файли у канонічні `<id>.mdc` — `findMissingMdcRefs` (викликається з `run-rule.mjs`) раніше падав, бо канонічні `.mdc` не містили `[name](./policy/<concern>/template/<file>)` для власних шаблонів. Bump rule versions: `text.mdc` `1.27` → `1.28`, `js-lint.mdc` `1.22` → `1.23`, `js-run.mdc` `1.8` → `1.9`.
|
|
22
|
+
|
|
23
|
+
## [1.13.26] - 2026-05-17
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `security` rule: міграція з gitleaks на TruffleHog. Канонічний `lint-security` тепер `trufflehog filesystem . --no-update --exclude-paths .trufflehog-exclude --results=verified,unknown --fail`; allowlist переїхав із TOML-файлу `.gitleaks.toml` (`[extend].useDefault=true` + `[allowlist].paths`) у plain-text `.trufflehog-exclude` (regex-pattern на рядок). Bump `security.mdc` version `1.1` → `2.0`.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- `npm/rules/security/fix/trufflehog/check.mjs` + `template/.trufflehog-exclude.snippet.txt` — JS-частина правила перевіряє існування `.trufflehog-exclude` та subset канонічних patterns через `checkTextSubset` (Rego не пасує plain-text-формату).
|
|
32
|
+
- `npm/rules/security/policy/lint_security_yml/` — Rego policy `security.lint_security_yml` із template `lint-security.yml.snippet.yml`; перевіряє, що `.github/workflows/lint-security.yml` містить крок з `uses: trufflesecurity/trufflehog@main`. У `security.mdc` inline-YAML замінено на template-link для single-source-of-truth (патерн із `php`/`style-lint`/`js-lint`). Workflow обовʼязковий (`target.json: required=true` + `missingMessage`), у корені cursor створено `.github/workflows/lint-security.yml` за каноном.
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
- `npm/rules/security/fix/gitleaks/` + `npm/rules/security/policy/gitleaks/` (концерн gitleaks повністю видалено разом з Rego policy `security.gitleaks`).
|
|
37
|
+
- Кореневий `.gitleaks.toml` (замінено на `.trufflehog-exclude`).
|
|
38
|
+
|
|
7
39
|
## [1.13.25] - 2026-05-17
|
|
8
40
|
|
|
9
41
|
### Added
|
package/package.json
CHANGED
package/rules/adr/adr.mdc
CHANGED
|
@@ -35,7 +35,7 @@ Stop-hook `normalize-decisions.sh` спрацьовує на тому самом
|
|
|
35
35
|
|
|
36
36
|
- Виходить миттєво, якщо чернеток (`session:` у frontmatter) менше ніж **`ADR_NORMALIZE_THRESHOLD`** (default 30).
|
|
37
37
|
- Виходить миттєво, якщо від попередньої спроби пройшло менше **`ADR_NORMALIZE_MIN_INTERVAL_HOURS`** годин (default 6) — щоб не крутитися щоразу, коли поріг постійний.
|
|
38
|
-
- Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default
|
|
38
|
+
- Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 10, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
|
|
39
39
|
- Виходить миттєво, якщо репозиторій у стані `MERGE_HEAD` / `rebase-*` — небезпечно правити файли посеред конфлікту.
|
|
40
40
|
- Виходить миттєво, якщо інший normalize-запуск тримає `flock` на `.claude/hooks/.normalize.lock` (тільки де `flock` доступний).
|
|
41
41
|
|
|
@@ -60,7 +60,7 @@ LLM повертає масив операцій:
|
|
|
60
60
|
| Змінна | Default | Призначення |
|
|
61
61
|
| --- | --- | --- |
|
|
62
62
|
| `ADR_NORMALIZE_THRESHOLD` | `30` | Поріг чернеток для запуску фази. |
|
|
63
|
-
| `ADR_NORMALIZE_BATCH` | `
|
|
63
|
+
| `ADR_NORMALIZE_BATCH` | `10` | Максимум чернеток у одному виклику LLM. |
|
|
64
64
|
| `ADR_NORMALIZE_MIN_INTERVAL_HOURS` | `6` | Мінімум між спробами (навіть якщо поріг). |
|
|
65
65
|
| `ADR_NORMALIZE_DRY` | `0` | `1` — лише лог запланованих операцій, без змін на диску. |
|
|
66
66
|
| `ADR_NORMALIZE_MODEL` | `sonnet` | Модель для `claude -p`. |
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Очікуваний формат URL — кластерний DNS-суфікс `<cluster>.internal`:
|
|
11
11
|
* - GKE / GCP: `http://<service>.<namespace>.svc.<cluster>.internal:<port>`
|
|
12
|
-
* приклад: `http://contract-h.ua-contract.svc.abie-ua.internal:8080`
|
|
12
|
+
* приклад: `http://contract-h-hl.ua-contract.svc.abie-ua.internal:8080`
|
|
13
13
|
*
|
|
14
14
|
* Сегменти беруться з `hasura/k8s/base/svc-hl.yaml` (`metadata.name` —
|
|
15
|
-
* має закінчуватись на `-h
|
|
15
|
+
* headless, має закінчуватись на `-h-hl`; див. `hasura.svc_hl` / k8s.svc_hl_yaml) і
|
|
16
|
+
* `hasura/k8s/base/namespace.yaml`
|
|
16
17
|
* (`metadata.name` — namespace). Якщо ці YAML є в репозиторії, у URL додатково
|
|
17
18
|
* звіряються конкретні `<service>` і `<namespace>` з ними.
|
|
18
19
|
*
|
package/rules/hasura/hasura.mdc
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила для директорії з hasura graphql-engine
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.2'
|
|
4
4
|
globs: "**/hasura/**,**/*.env"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
---
|
|
@@ -18,10 +18,10 @@ HASURA_GRAPHQL_ENDPOINT=https://vybeerai.com.ua/contract/ql
|
|
|
18
18
|
Правильне значення:
|
|
19
19
|
|
|
20
20
|
```env
|
|
21
|
-
HASURA_GRAPHQL_ENDPOINT=http://contract-h.ua-contract.svc.abie-ua.internal:8080
|
|
21
|
+
HASURA_GRAPHQL_ENDPOINT=http://contract-h-hl.ua-contract.svc.abie-ua.internal:8080
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
де `contract-h` — це `metadata.name`
|
|
24
|
+
де `contract-h-hl` — це `metadata.name` headless Service з `hasura/k8s/base/svc-hl.yaml` (пара з clusterIP `contract-h` у `svc.yaml`, якщо є; узгоджено з k8s: суфікс `-hl` на базі `-h`), а `ua-contract` — `metadata.name` namespace з `hasura/k8s/base/namespace.yaml`.
|
|
25
25
|
|
|
26
26
|
Правило застосовується для проєктів **nitra** (у кореневому `package.json` `"repository": "https://github.com/nitra/*"`) і **abie** (`"repository": "https://github.com/abinbevefes/*"`); для інших репозиторіїв перевірка пропускається.
|
|
27
27
|
|
|
@@ -1,28 +1,50 @@
|
|
|
1
|
-
#
|
|
2
|
-
# `
|
|
3
|
-
#
|
|
1
|
+
# Іменування Service у `hasura/k8s/base/svc.yaml` та `svc-hl.yaml`, узгоджене з
|
|
2
|
+
# `k8s.svc_hl_yaml` / `k8s.svc_yaml` (пара clusterIP + headless під `k8s/**/`).
|
|
3
|
+
#
|
|
4
|
+
# Hasura-конвенція: базовий сегмент закінчується на `-h`; headless додає `-hl`
|
|
5
|
+
# → повне ім'я `*-h-hl` (також задовольняє k8s-вимогу суфікса `-hl`).
|
|
4
6
|
#
|
|
5
7
|
# Запуск (локально):
|
|
6
|
-
# conftest test hasura/k8s/base/svc-hl.yaml -p npm/policy/
|
|
8
|
+
# conftest test hasura/k8s/base/svc-hl.yaml -p npm/rules/hasura/policy/svc_hl \
|
|
7
9
|
# --namespace hasura.svc_hl
|
|
8
10
|
#
|
|
9
|
-
#
|
|
10
|
-
# з `<service>.<namespace>.svc.<cluster>` через regex по всьому дереву репо, gating
|
|
11
|
-
# на `repository` у кореневому `package.json`) — у JS: вона потребує текстового
|
|
12
|
-
# парсингу `.env`-файлів, обходу дерева й cross-file resolution. JS authoritative;
|
|
13
|
-
# ця Rego — додатковий gate (JS неявно перевіряє суфікс через звірку URL).
|
|
14
|
-
#
|
|
15
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
16
|
-
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
17
|
-
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
11
|
+
# Cross-file (`HASURA_GRAPHQL_ENDPOINT` ↔ YAML) — `fix/internal_urls/check.mjs`.
|
|
18
12
|
package hasura.svc_hl
|
|
19
13
|
|
|
20
14
|
import rego.v1
|
|
21
15
|
|
|
16
|
+
# Суфікс clusterIP Service у hasura/k8s/base (і база для пари з svc-hl.yaml).
|
|
17
|
+
hasura_cluster_suffix := "-h"
|
|
18
|
+
|
|
19
|
+
# Headless: `<cluster-name>-hl`, напр. `db-h` → `db-h-hl`.
|
|
20
|
+
hasura_headless_suffix := "-h-hl"
|
|
21
|
+
|
|
22
|
+
service_is_headless if {
|
|
23
|
+
input.kind == "Service"
|
|
24
|
+
spec := object.get(input, "spec", {})
|
|
25
|
+
is_object(spec)
|
|
26
|
+
spec.clusterIP == "None"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
service_is_headless
|
|
31
|
+
name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
32
|
+
name != ""
|
|
33
|
+
not endswith(name, hasura_headless_suffix)
|
|
34
|
+
msg := sprintf(
|
|
35
|
+
"hasura svc-hl.yaml: headless Service %q має закінчуватись на `%s` (узгоджено з k8s.svc_hl_yaml `-hl`; hasura.mdc)",
|
|
36
|
+
[name, hasura_headless_suffix],
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
deny contains msg if {
|
|
23
41
|
input.kind == "Service"
|
|
42
|
+
not service_is_headless
|
|
24
43
|
name := object.get(object.get(input, "metadata", {}), "name", "")
|
|
25
44
|
name != ""
|
|
26
|
-
not endswith(name,
|
|
27
|
-
msg := sprintf(
|
|
45
|
+
not endswith(name, hasura_cluster_suffix)
|
|
46
|
+
msg := sprintf(
|
|
47
|
+
"hasura svc.yaml: clusterIP Service %q має закінчуватись на `%s` (hasura.mdc / k8s.mdc)",
|
|
48
|
+
[name, hasura_cluster_suffix],
|
|
49
|
+
)
|
|
28
50
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Перевірка JavaScript коду
|
|
3
3
|
globs: "**/{.oxlintrc.json,eslint.config.js,.jscpd.json,knip.json,package.json},**/*.{js,mjs,cjs,jsx,ts,tsx}"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
version: '1.
|
|
5
|
+
version: '1.23'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
**oxlint**, **ESLint**, **jscpd**, **knip**. У скрипті **`lint-js`** і в CI — **`bunx oxlint`**, **`bunx eslint`**, **`bunx jscpd`**, **`bunx knip`** (у CI без **`--fix`** для oxlint/eslint — див. приклад workflow нижче). Без **prettier** і **@nitra/prettier-config**. У **`devDependencies`** має бути **`@nitra/eslint-config` мінімум `^3.9.2`** (з **3.8.0** правило `no-restricted-syntax` забороняє `for...in`; з **3.9.2** у `getConfig` вбудовано ignore для **`**/adr/**`** — ADR-документи не валідуються ESLint, локально цей glob додавати не потрібно; також транзитивно йде **`@e18e/eslint-plugin`** для oxlint); пакет **`@e18e/eslint-plugin`** окремо не додавай. Пакети oxlint/eslint/jscpd/knip не додавай без потреби монорепо.
|
|
@@ -21,6 +21,10 @@ version: '1.22'
|
|
|
21
21
|
}
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
Канон `type` + `scripts.lint-js` (substring requirement): [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
|
|
25
|
+
|
|
26
|
+
У `.vscode/extensions.json` `recommendations` мають містити `dbaeumer.vscode-eslint`, `github.vscode-github-actions`, `oxc.oxc-vscode`: [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
|
|
27
|
+
|
|
24
28
|
У корені має бути **`.oxlintrc.json`**, який **збігається з каноном** oxlint з пакета **`@nitra/cursor`**: файл **`npm/scripts/utils/oxlint-canonical.json`** (plugins, jsPlugins з **`@e18e/eslint-plugin`**, categories, повний набір **rules** із канону — додаткові записи в **`rules`** дозволені; також **`settings`**, **`env`**, **`globals`**). Поле **`ignorePatterns`** працює як **`rules`**: канонічні патерни з **`oxlint-canonical.json`** (наразі **`**/schema.graphql`**, **`**/auto-imports.d.ts`**) мають бути присутні, додаткові локальні glob-и дозволені. Оновити канон можна з репозиторію пакета або скопіювавши файл після **`bun ./scripts/utils/rebuild-oxlint-canonical.mjs`** (джерело правил — **`oxlint-rules.tsv`** + скелет **`oxlint-canonical-skeleton.json`**). Модуль **`@e18e/eslint-plugin`** не оголошуй окремо в **`package.json`** — він уже в залежностях **`@nitra/eslint-config`** (з **3.8.0**), oxlint підвантажує його з **`node_modules`**.
|
|
25
29
|
|
|
26
30
|
Мінімум для розуміння структури (реальний корінь конфігу має збігатися з каноном повністю):
|
|
@@ -48,6 +52,8 @@ version: '1.22'
|
|
|
48
52
|
}
|
|
49
53
|
```
|
|
50
54
|
|
|
55
|
+
Канон базових ключів `.jscpd.json` (`gitignore`, `exitCode`, `reporters`, `minLines`): [.jscpd.json.snippet.json](./policy/jscpd/template/.jscpd.json.snippet.json)
|
|
56
|
+
|
|
51
57
|
```text title=".gitignore (фрагмент)"
|
|
52
58
|
.claude/worktrees/
|
|
53
59
|
```
|
|
@@ -122,6 +128,8 @@ jobs:
|
|
|
122
128
|
bunx knip --no-config-hints
|
|
123
129
|
```
|
|
124
130
|
|
|
131
|
+
Канон workflow `.github/workflows/lint-js.yml`: [lint-js.yml.snippet.yml](./policy/lint_js_yml/template/lint-js.yml.snippet.yml)
|
|
132
|
+
|
|
125
133
|
Перед **`./.github/actions/setup-bun-deps`** — **`actions/checkout@v6`** (див. **ga.mdc**). Composite: Node 24, Bun, кеш, `bun install --frozen-lockfile`.
|
|
126
134
|
|
|
127
135
|
Один workflow на лінт JS; зайвий `lint.yml` з тими самими кроками — прибери.
|
package/rules/js-run/js-run.mdc
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Це правила для backend проектів на JavaScript/Node.js, сюди входять і job і WEB сервери.
|
|
3
3
|
globs: "**/package.json,**/jsconfig.json,**/src/**/*.{js,mjs,cjs,ts,tsx}"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
version: '1.
|
|
5
|
+
version: '1.9'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
## Область застосування
|
|
@@ -46,16 +46,20 @@ readme.md
|
|
|
46
46
|
}
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Канон: [jsconfig.json.snippet.json](./policy/jsconfig/template/jsconfig.json.snippet.json)
|
|
50
|
+
|
|
49
51
|
Якщо пакет не слідує структурі з `src/` (наприклад, лише `scripts/` у корені) — ця вимога не застосовується; для типових сервісів із `src/` файл обов’язковий і має збігатися з каноном.
|
|
50
52
|
|
|
51
53
|
## Використання @nitra/pino
|
|
52
54
|
|
|
53
55
|
Проект використовує @nitra/pino для логування.
|
|
54
|
-
Якщо в проекті присутній @nitra/bunyan, то він повинен бути замінений на @nitra/pino — як у `package.json`, так і в коді: усі `import` / `require` / динамічні `import()` з `@nitra/bunyan` (і застарілого `bunyan`) треба замінити на `@nitra/pino` і за потреби адаптувати виклики під його API.
|
|
56
|
+
Якщо в проекті присутній @nitra/bunyan, то він повинен бути замінений на @nitra/pino — як у `package.json`, так і в коді: усі `import` / `require` / динамічні `import()` з `@nitra/bunyan` (і застарілого `bunyan`) треба замінити на `@nitra/pino` і за потреби адаптувати виклики під його API. Канон заборонених `dependencies` / `devDependencies` (`bunyan`, `@nitra/bunyan`): [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
|
|
55
57
|
|
|
56
58
|
В **/k8s/base/configmap.yaml повинен бути заданий OTEL_RESOURCE_ATTRIBUTES: 'service.name=<project_name>,service.namespace=<project_namespace>'
|
|
57
59
|
а в директоріях з kustomize повинні бути перевизначені значення OTEL_RESOURCE_ATTRIBUTES і в них service.namespace повинен відповідати namespace, в якому знаходиться дана директорія.
|
|
58
60
|
|
|
61
|
+
Канон обовʼязкових substring у `data.OTEL_RESOURCE_ATTRIBUTES` (`service.name=`, `service.namespace=`): [configmap.yaml.contains.yml](./policy/configmap/template/configmap.yaml.contains.yml)
|
|
62
|
+
|
|
59
63
|
## Внутрішні аліаси
|
|
60
64
|
|
|
61
65
|
Якщо в проекті є підключення до баз даних, зовнішніх graphql на кшталт:
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FS-частина правила `security`.
|
|
3
|
+
*
|
|
4
|
+
* Перевіряє:
|
|
5
|
+
* - наявність `package.json` (структуру валідує policy security.package_json);
|
|
6
|
+
* - наявність `.trufflehog-exclude` у корені та subset канонічних patterns
|
|
7
|
+
* (text-subset, бо `.trufflehog-exclude` — plain text, не структурований).
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs'
|
|
10
|
+
import { readFile } from 'node:fs/promises'
|
|
11
|
+
import { dirname, join } from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
14
|
+
import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
|
|
15
|
+
import { checkTextSubset } from '../../../../scripts/utils/template.mjs'
|
|
16
|
+
|
|
17
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
18
|
+
const SNIPPET_PATH = join(HERE, 'template', '.trufflehog-exclude.snippet.txt')
|
|
19
|
+
|
|
20
|
+
export async function check() {
|
|
21
|
+
const reporter = createCheckReporter()
|
|
22
|
+
const { pass, fail } = reporter
|
|
23
|
+
|
|
24
|
+
if (!existsSync('package.json')) {
|
|
25
|
+
fail('package.json не знайдено в корені — додай (security.mdc)')
|
|
26
|
+
return reporter.getExitCode()
|
|
27
|
+
}
|
|
28
|
+
pass('package.json є (структуру перевіряє Rego)')
|
|
29
|
+
|
|
30
|
+
if (!existsSync('.trufflehog-exclude')) {
|
|
31
|
+
fail('.trufflehog-exclude не знайдено в корені — додай за каноном (security.mdc)')
|
|
32
|
+
return reporter.getExitCode()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const actual = await readFile('.trufflehog-exclude', 'utf8')
|
|
36
|
+
const template = await readFile(SNIPPET_PATH, 'utf8')
|
|
37
|
+
const errors = checkTextSubset(actual, template, {
|
|
38
|
+
targetPath: '.trufflehog-exclude',
|
|
39
|
+
source: 'security.mdc'
|
|
40
|
+
})
|
|
41
|
+
for (const msg of errors) fail(msg)
|
|
42
|
+
if (errors.length === 0) pass('.trufflehog-exclude містить канонічні patterns')
|
|
43
|
+
|
|
44
|
+
return reporter.getExitCode()
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Перевірка `.github/workflows/lint-security.yml` (security.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Канон надходить через --data: { "template": { "snippet": ... } }, побудоване
|
|
4
|
+
# з template/lint-security.yml.snippet.yml. З нього збирається перелік
|
|
5
|
+
# `uses:` action-refs security-job (виключаючи універсальні `actions/*`, які
|
|
6
|
+
# валідує `ga.workflow_common`).
|
|
7
|
+
#
|
|
8
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
9
|
+
package security.lint_security_yml
|
|
10
|
+
|
|
11
|
+
import rego.v1
|
|
12
|
+
|
|
13
|
+
# Очікувані action-uses із template (не-`actions/*`).
|
|
14
|
+
expected_uses_blob := concat("\n", [u |
|
|
15
|
+
some step in data.template.snippet.jobs.security.steps
|
|
16
|
+
u := object.get(step, "uses", "")
|
|
17
|
+
u != ""
|
|
18
|
+
not startswith(u, "actions/")
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
# Усі `uses:` із input workflow.
|
|
22
|
+
all_uses_text := concat("\n", [u |
|
|
23
|
+
some job in object.get(input, "jobs", {})
|
|
24
|
+
some step in object.get(job, "steps", [])
|
|
25
|
+
u := object.get(step, "uses", "")
|
|
26
|
+
u != ""
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
expected_uses_blob != ""
|
|
31
|
+
not contains(all_uses_text, expected_uses_blob)
|
|
32
|
+
msg := sprintf("lint-security.yml: відсутній крок з uses %q (security.mdc)", [expected_uses_blob])
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Lint Security
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- dev
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
pull_request:
|
|
10
|
+
branches:
|
|
11
|
+
- dev
|
|
12
|
+
- main
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ${{ github.ref }}-${{ github.workflow }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
security:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v6
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
fetch-depth: 0
|
|
28
|
+
|
|
29
|
+
- uses: trufflesecurity/trufflehog@main
|
|
30
|
+
with:
|
|
31
|
+
extra_args: --results=verified,unknown
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"dependencies": { "
|
|
3
|
-
"devDependencies": { "
|
|
2
|
+
"dependencies": { "trufflehog": "глобальний CLI — не додавай у dependencies" },
|
|
3
|
+
"devDependencies": { "trufflehog": "глобальний CLI — не додавай у devDependencies" }
|
|
4
4
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{ "scripts": { "lint-security": "
|
|
1
|
+
{ "scripts": { "lint-security": "trufflehog filesystem . --no-update --exclude-paths .trufflehog-exclude --results=verified,unknown --fail" } }
|
|
@@ -1,58 +1,37 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Локальний та CI-секюріті-лінт через
|
|
3
|
-
globs: "**/.
|
|
2
|
+
description: Локальний та CI-секюріті-лінт через TruffleHog — скрипт `lint-security`, `.trufflehog-exclude`, інтеграція в агрегований `lint`
|
|
3
|
+
globs: "**/.trufflehog-exclude,**/package.json,**/.github/workflows/**/*.yml"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
version: '
|
|
5
|
+
version: '2.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
[
|
|
8
|
+
[TruffleHog](https://github.com/trufflesecurity/trufflehog) — глобальний CLI (як `shellcheck`, `conftest`); **не** додавай до `dependencies`/`devDependencies`.
|
|
9
9
|
|
|
10
10
|
## Канон `package.json#scripts`
|
|
11
11
|
|
|
12
12
|
- `lint-security` скрипт: [package.json.snippet.json](./policy/package_json/template/package.json.snippet.json)
|
|
13
13
|
- `lint` агрегатор повинен містити: [package.json.contains.json](./policy/package_json/template/package.json.contains.json)
|
|
14
|
-
- Заборонено `
|
|
14
|
+
- Заборонено `trufflehog` у `dependencies`/`devDependencies`: [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
|
|
15
15
|
|
|
16
16
|
**Зауваження:**
|
|
17
17
|
|
|
18
|
-
- `
|
|
19
|
-
- `--no-
|
|
18
|
+
- `trufflehog filesystem .` — сканує робоче дерево як директорію (включно з untracked/gitignored файлами); підкоманда `git file://.` лишається на CI для аудиту історії.
|
|
19
|
+
- `--no-update` — вимикає self-update check (CI-friendly).
|
|
20
|
+
- `--exclude-paths .trufflehog-exclude` — файл з regex-patterns, які треба пропускати (аналог `[allowlist].paths` із gitleaks).
|
|
21
|
+
- `--results=verified,unknown` — показує лише верифіковані секрети + ті, що TruffleHog не зміг перевірити (`unverified` дублікат відсіюється).
|
|
22
|
+
- `--fail` — exit-code `183` за наявності знахідок (потрібно, щоб `bun run lint` падав).
|
|
20
23
|
- Позиція в `lint`: за конвенцією після інших `lint-*` і перед `oxfmt`.
|
|
21
24
|
|
|
22
|
-
## `.
|
|
25
|
+
## `.trufflehog-exclude` (рекомендована основа)
|
|
23
26
|
|
|
24
|
-
Канон (допускає розширення
|
|
27
|
+
Канон (допускає розширення): [.trufflehog-exclude.snippet.txt](./fix/trufflehog/template/.trufflehog-exclude.snippet.txt)
|
|
25
28
|
|
|
26
|
-
**Важливо:**
|
|
29
|
+
**Важливо:** один regex-pattern на рядок, без TOML-обгортки; коментарі починаються з `#`.
|
|
27
30
|
|
|
28
|
-
##
|
|
31
|
+
## CI: `.github/workflows/lint-security.yml`
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
Workflow обовʼязковий — забезпечує незалежний скан секретів на push/PR (агрегований `lint` локально + окремий fail-fast job на CI).
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
name: Lint Security
|
|
35
|
+
- Канон: [lint-security.yml.snippet.yml](./policy/lint_security_yml/template/lint-security.yml.snippet.yml)
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
push:
|
|
37
|
-
branches: [dev, main]
|
|
38
|
-
pull_request:
|
|
39
|
-
branches: [dev, main]
|
|
40
|
-
|
|
41
|
-
concurrency:
|
|
42
|
-
group: ${{ github.ref }}-${{ github.workflow }}
|
|
43
|
-
cancel-in-progress: true
|
|
44
|
-
|
|
45
|
-
jobs:
|
|
46
|
-
security:
|
|
47
|
-
runs-on: ubuntu-latest
|
|
48
|
-
permissions:
|
|
49
|
-
contents: read
|
|
50
|
-
steps:
|
|
51
|
-
- uses: actions/checkout@v6
|
|
52
|
-
with:
|
|
53
|
-
persist-credentials: false
|
|
54
|
-
fetch-depth: 0
|
|
55
|
-
- uses: gitleaks/gitleaks-action@v2
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Для повного скану git-історії потрібен `fetch-depth: 0`.
|
|
37
|
+
Перевіряється policy `security.lint_security_yml`: серед `uses:` має бути крок з `trufflesecurity/trufflehog@main`. Універсальні workflow-перевірки (checkout, permissions, persist-credentials) — у `ga.workflow_common`. Для повного скану історії потрібен `fetch-depth: 0`.
|
package/rules/text/text.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Обробка та перевірка текстових файлів, oxfmt, cspell, shellcheck (sh), dotenv-linter (.env*), markdownlint-cli2, v8r, CI
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.28'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
**oxfmt** (`.oxfmtrc.json`, редактор), **cspell**, **shellcheck** (tracked `*.sh` у `lint-text`), **[dotenv-linter](https://dotenv-linter.github.io/)** (`.env*` у `lint-text`), **markdownlint-cli2**, **[v8r](https://chris48s.github.io/v8r/)** ([Schema Store](https://www.schemastore.org/)), розширення **DavidAnson.vscode-markdownlint** / **timonwong.shellcheck**, workflow **`lint-text`**.
|
|
@@ -20,6 +20,8 @@ version: '1.27'
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
Канон `recommendations` (substring requirement): [extensions.json.snippet.json](./policy/vscode_extensions/template/extensions.json.snippet.json)
|
|
24
|
+
|
|
23
25
|
```json title=".vscode/settings.json"
|
|
24
26
|
{
|
|
25
27
|
"files.associations": {
|
|
@@ -79,6 +81,8 @@ version: '1.27'
|
|
|
79
81
|
}
|
|
80
82
|
```
|
|
81
83
|
|
|
84
|
+
Канон `editor.formatOnSave` + `editor.defaultFormatter` для основних мов: [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
|
|
85
|
+
|
|
82
86
|
У корені проєкту має бути файл з правилами форматування для **oxfmt**:
|
|
83
87
|
|
|
84
88
|
```json title=".oxfmtrc.json"
|
|
@@ -105,9 +109,11 @@ version: '1.27'
|
|
|
105
109
|
}
|
|
106
110
|
```
|
|
107
111
|
|
|
112
|
+
Канон мінімального набору ключів і `ignorePatterns`: [.oxfmtrc.json.snippet.json](./policy/oxfmtrc/template/.oxfmtrc.json.snippet.json)
|
|
113
|
+
|
|
108
114
|
Поле **`ignorePatterns`** обовʼязкове: у масиві мають бути **`**/hasura/metadata/**`**, **`**/schema.graphql`** і **`**/auto-imports.d.ts`**; інші glob-и додавай за потреби (згенеровані каталоги тощо) — канон задає мінімум, локальні розширення дозволені.
|
|
109
115
|
|
|
110
|
-
Також потрібно прибрати, якщо є в проєкті, модуль **`@nitra/prettier-config`**, **prettier** та всі виклики prettier і налаштування для нього.
|
|
116
|
+
Також потрібно прибрати, якщо є в проєкті, модуль **`@nitra/prettier-config`**, **prettier** та всі виклики prettier і налаштування для нього. Канон заборонених top-level/`dependencies`/`devDependencies` (prettier, `@nitra/prettier-config`, `markdownlint-cli2`): [package.json.deny.json](./policy/package_json/template/package.json.deny.json)
|
|
111
117
|
|
|
112
118
|
Завжди пиши **JSDoc** до функцій та методів.
|
|
113
119
|
|
|
@@ -156,6 +162,8 @@ version: '1.27'
|
|
|
156
162
|
}
|
|
157
163
|
```
|
|
158
164
|
|
|
165
|
+
Канон: [.markdownlint-cli2.jsonc.snippet.jsonc](./policy/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc)
|
|
166
|
+
|
|
159
167
|
**MD041** off навмисно (`.mdc` з frontmatter). Деталі — [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2).
|
|
160
168
|
|
|
161
169
|
## Cspell
|
|
@@ -238,6 +246,8 @@ jobs:
|
|
|
238
246
|
}
|
|
239
247
|
```
|
|
240
248
|
|
|
249
|
+
Канон базових ключів `.cspell.json` (`version`, `ignorePaths`): [.cspell.json.snippet.json](./policy/cspell/template/.cspell.json.snippet.json). Обовʼязковий запис у `import`: [.cspell.json.contains.json](./policy/cspell/template/.cspell.json.contains.json). Заборонено імпортувати окремі `@cspell/dict-*` у `.cspell.json`: [.cspell.json.deny.json](./policy/cspell/template/.cspell.json.deny.json).
|
|
250
|
+
|
|
241
251
|
```json title="package.json"
|
|
242
252
|
{
|
|
243
253
|
"scripts": {
|
|
@@ -28,8 +28,9 @@ async function parseByExt(path) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function stripJsonComments(s) {
|
|
31
|
-
//
|
|
32
|
-
|
|
31
|
+
// Match string literals OR comments. Strings are returned unchanged so we never
|
|
32
|
+
// strip `/*` / `//` / `*/` that appear inside values (e.g. glob `**/node_modules/**`).
|
|
33
|
+
return s.replace(/"(?:\\.|[^"\\])*"|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, m => (m.startsWith('"') ? m : ''))
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
async function walk(dir, base = dir) {
|
|
@@ -55,11 +55,11 @@ description: >-
|
|
|
55
55
|
- Відкотити конкретний файл: `git checkout -- docs/adr/<file>` (для `rewrite` цього мало — треба ще `git restore --staged` і `rm` нового).
|
|
56
56
|
- Відкотити весь батч: `git checkout -- docs/adr/ && git clean -f docs/adr/` (видалить і untracked rewrite-результати).
|
|
57
57
|
|
|
58
|
-
5. **Повторити для наступного батчу**, якщо чернеток ще багато. Кожен запуск обробляє до `ADR_NORMALIZE_BATCH` файлів (default
|
|
58
|
+
5. **Повторити для наступного батчу**, якщо чернеток ще багато. Кожен запуск обробляє до `ADR_NORMALIZE_BATCH` файлів (default 10, найстарші за часовою позначкою у назві).
|
|
59
59
|
|
|
60
60
|
## Tuning через ENV
|
|
61
61
|
|
|
62
|
-
- `ADR_NORMALIZE_BATCH=
|
|
62
|
+
- `ADR_NORMALIZE_BATCH=30` — більший батч (менше викликів LLM, більше токенів за раз).
|
|
63
63
|
- `ADR_NORMALIZE_MODEL=opus` — інша модель `claude -p`.
|
|
64
64
|
- `ADR_NORMALIZE_CURSOR_MODEL=…` — інша модель для cursor-agent fallback.
|
|
65
65
|
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FS-частина правила `security`.
|
|
3
|
-
*
|
|
4
|
-
* Перевіряє:
|
|
5
|
-
* - наявність `package.json` (структуру валідує Rego);
|
|
6
|
-
* - контекстне pass-повідомлення для JS-концерну.
|
|
7
|
-
*
|
|
8
|
-
* Наявність і вміст `.gitleaks.toml` (`[extend].useDefault = true`) тепер
|
|
9
|
-
* перевіряє policy `security.gitleaks`.
|
|
10
|
-
*/
|
|
11
|
-
import { existsSync } from 'node:fs'
|
|
12
|
-
|
|
13
|
-
import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mjs'
|
|
14
|
-
|
|
15
|
-
export async function check() {
|
|
16
|
-
const reporter = createCheckReporter()
|
|
17
|
-
const { pass, fail } = reporter
|
|
18
|
-
if (!existsSync('package.json')) {
|
|
19
|
-
fail('package.json не знайдено в корені — додай (security.mdc)')
|
|
20
|
-
return reporter.getExitCode()
|
|
21
|
-
}
|
|
22
|
-
pass('package.json є (структуру перевіряє Rego)')
|
|
23
|
-
pass('.gitleaks.toml перевіряє npx @nitra/cursor check → security.gitleaks')
|
|
24
|
-
return reporter.getExitCode()
|
|
25
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Перевірка `.gitleaks.toml` для security (security.mdc).
|
|
2
|
-
#
|
|
3
|
-
# Канонічна мінімальна вимога: `[extend].useDefault = true`, щоб локальний
|
|
4
|
-
# конфіг не вимикав стандартні правила gitleaks. Додаткові локальні правила
|
|
5
|
-
# дозволені.
|
|
6
|
-
#
|
|
7
|
-
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
8
|
-
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
9
|
-
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
10
|
-
package security.gitleaks
|
|
11
|
-
|
|
12
|
-
import rego.v1
|
|
13
|
-
|
|
14
|
-
deny contains msg if {
|
|
15
|
-
object.get(object.get(input, "extend", {}), "useDefault", null) != true
|
|
16
|
-
msg := ".gitleaks.toml: [extend].useDefault має бути true (security.mdc)"
|
|
17
|
-
}
|