@nitra/cursor 1.17.3 → 1.18.0

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.
@@ -34,6 +34,62 @@ mkdir -p "$ADR_DIR" "$LOG_DIR"
34
34
 
35
35
  log() { printf '%s %s\n' "$(date -Iseconds)" "$*" >> "$LOG"; }
36
36
 
37
+ # Структурний скіп ADR-генерації для "tooling-only" сесій.
38
+ # Вхід: рядки-шляхи у stdin (один шлях на лінію), відносні до $PROJECT_ROOT
39
+ # або абсолютні з префіксом $PROJECT_ROOT (нормалізуємо тут).
40
+ # Вихід: 0 — усі шляхи в allowlist; 1 — є хоч один змістовний шлях.
41
+ # Bash 3.2: без mapfile/асоц. масивів.
42
+ is_tooling_only_change() {
43
+ local proj="$1"
44
+ local had_file=0
45
+ local f rel
46
+ while IFS= read -r f; do
47
+ [ -z "$f" ] && continue
48
+ had_file=1
49
+ case "$f" in
50
+ "$proj"/*) rel="${f#"$proj"/}" ;;
51
+ /*) return 1 ;;
52
+ *) rel="$f" ;;
53
+ esac
54
+ case "$rel" in
55
+ .cspell.json) ;;
56
+ docs/adr/*.md) ;;
57
+ AGENTS.md|CLAUDE.md) ;;
58
+ CHANGELOG.md) ;;
59
+ */CHANGELOG.md) ;;
60
+ package.json|*/package.json)
61
+ if ! git_diff_only_version_field "$proj" "$rel"; then
62
+ return 1
63
+ fi
64
+ ;;
65
+ *) return 1 ;;
66
+ esac
67
+ done
68
+ [ "$had_file" = "1" ] && return 0
69
+ return 1
70
+ }
71
+
72
+ # Допоміжна: чи git-diff для файлу торкається ЛИШЕ рядків з `"version":`.
73
+ # Поза git-репо або при помилці — вертаємо 1 (не tooling).
74
+ git_diff_only_version_field() {
75
+ local proj="$1" path="$2"
76
+ [ -d "$proj/.git" ] || return 1
77
+ local diff
78
+ diff=$(cd "$proj" && git diff HEAD --unified=0 -- "$path" 2>/dev/null) || return 1
79
+ [ -z "$diff" ] && return 1
80
+ local line
81
+ while IFS= read -r line; do
82
+ case "$line" in
83
+ '+++ '*|'--- '*|'@@ '*|'') continue ;;
84
+ [+-]*'"version":'*) continue ;;
85
+ [+-]*) return 1 ;;
86
+ esac
87
+ done <<EOF
88
+ $diff
89
+ EOF
90
+ return 0
91
+ }
92
+
37
93
  log "fired: $SESSION_ID"
38
94
 
39
95
  if [[ -z "$TRANSCRIPT_PATH" || ! -f "$TRANSCRIPT_PATH" ]]; then
@@ -86,6 +142,29 @@ if [[ -z "$TRANSCRIPT" ]]; then
86
142
  exit 0
87
143
  fi
88
144
 
145
+ # Structural skip: якщо в сесії змінювалися лише tooling-файли — не викликаємо LLM.
146
+ # ENV `ADR_NORMALIZE_SKIP_TOOLING_ONLY=0` вимикає скіп.
147
+ if [[ "${ADR_NORMALIZE_SKIP_TOOLING_ONLY:-1}" = "1" ]]; then
148
+ CHANGED_FILES=$(jq -r '
149
+ select(.type == "assistant" or .role == "assistant")
150
+ | .message as $m
151
+ | ($m.content // [])
152
+ | if type == "array" then
153
+ map(select(.type == "tool_use" and (.name == "Edit" or .name == "Write" or .name == "MultiEdit"))
154
+ | .input.file_path // empty)
155
+ | .[]
156
+ else empty end
157
+ ' "$TRANSCRIPT_PATH" 2>/dev/null | sort -u || true)
158
+
159
+ if [[ -n "$CHANGED_FILES" ]]; then
160
+ if printf '%s\n' "$CHANGED_FILES" | is_tooling_only_change "$PROJECT_ROOT"; then
161
+ log " → skipping ADR capture: tooling-only session"
162
+ log " files: $(printf '%s' "$CHANGED_FILES" | tr '\n' ' ')"
163
+ exit 0
164
+ fi
165
+ fi
166
+ fi
167
+
89
168
  PROMPT=$(cat <<'EOF'
90
169
  You analyze an AI coding session transcript and produce durable decision documentation.
91
170
 
@@ -39,6 +39,65 @@ mkdir -p "$LOG_DIR"
39
39
 
40
40
  log() { printf '%s %s\n' "$(date -Iseconds)" "$*" >> "$LOG"; }
41
41
 
42
+ # Структурний скіп ADR-нормалізації для "tooling-only" сесій.
43
+ # Дублікат із capture-decisions.sh: `.claude-template/hooks/` копіюється плоско,
44
+ # спільний helper-файл туди не вписується.
45
+ is_tooling_only_change() {
46
+ proj="$1"
47
+ had_file=0
48
+ while IFS= read -r f; do
49
+ [ -z "$f" ] && continue
50
+ had_file=1
51
+ case "$f" in
52
+ "$proj"/*) rel="${f#"$proj"/}" ;;
53
+ /*) return 1 ;;
54
+ *) rel="$f" ;;
55
+ esac
56
+ case "$rel" in
57
+ .cspell.json) ;;
58
+ docs/adr/*.md) ;;
59
+ AGENTS.md|CLAUDE.md) ;;
60
+ CHANGELOG.md) ;;
61
+ */CHANGELOG.md) ;;
62
+ package.json|*/package.json)
63
+ if ! git_diff_only_version_field "$proj" "$rel"; then
64
+ return 1
65
+ fi
66
+ ;;
67
+ *) return 1 ;;
68
+ esac
69
+ done
70
+ [ "$had_file" = "1" ] && return 0
71
+ return 1
72
+ }
73
+
74
+ # Допоміжна: чи git-diff для файлу торкається ЛИШЕ рядків з `"version":`.
75
+ git_diff_only_version_field() {
76
+ proj="$1"; path="$2"
77
+ [ -d "$proj/.git" ] || return 1
78
+ diff=$(cd "$proj" && git diff HEAD --unified=0 -- "$path" 2>/dev/null) || return 1
79
+ [ -z "$diff" ] && return 1
80
+ while IFS= read -r line; do
81
+ case "$line" in
82
+ '+++ '*|'--- '*|'@@ '*|'') continue ;;
83
+ [+-]*'"version":'*) continue ;;
84
+ [+-]*) return 1 ;;
85
+ esac
86
+ done <<EOF
87
+ $diff
88
+ EOF
89
+ return 0
90
+ }
91
+
92
+ # Витягає поле `transcript:` з YAML frontmatter ADR-чернетки.
93
+ draft_transcript_path() {
94
+ awk '
95
+ NR==1 && /^---$/ { fm=1; next }
96
+ fm && /^---$/ { exit }
97
+ fm && /^transcript: / { sub(/^transcript: /, ""); print; exit }
98
+ ' "$1" 2>/dev/null
99
+ }
100
+
42
101
  # Skip if repo is mid-rebase / mid-merge — editing files now would tangle the user.
43
102
  if [ -d "$PROJECT_ROOT/.git" ]; then
44
103
  for marker in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD rebase-apply rebase-merge; do
@@ -120,6 +179,45 @@ head -n "$BATCH_SIZE" "$DRAFTS_LIST" > "$BATCH_LIST"
120
179
  BATCH_COUNT=$(wc -l < "$BATCH_LIST" | tr -d ' ')
121
180
  log "batch size: $BATCH_COUNT"
122
181
 
182
+ # Structural skip: чернетки з tooling-only сесій видаляємо без виклику LLM.
183
+ if [ "${ADR_NORMALIZE_SKIP_TOOLING_ONLY:-1}" = "1" ]; then
184
+ FILTERED_LIST="$TMP_DIR/batch-filtered.txt"
185
+ : > "$FILTERED_LIST"
186
+ TOOLING_REMOVED=0
187
+ while IFS= read -r draft; do
188
+ [ -f "$draft" ] || continue
189
+ tpath=$(draft_transcript_path "$draft")
190
+ if [ -n "$tpath" ] && [ -f "$tpath" ]; then
191
+ changed=$(jq -r '
192
+ select(.type == "assistant" or .role == "assistant")
193
+ | .message as $m
194
+ | ($m.content // [])
195
+ | if type == "array" then
196
+ map(select(.type == "tool_use" and (.name == "Edit" or .name == "Write" or .name == "MultiEdit"))
197
+ | .input.file_path // empty)
198
+ | .[]
199
+ else empty end
200
+ ' "$tpath" 2>/dev/null | sort -u || true)
201
+ if [ -n "$changed" ] && printf '%s\n' "$changed" | is_tooling_only_change "$PROJECT_ROOT"; then
202
+ rm -f -- "$draft"
203
+ log "tooling-only delete: $(basename "$draft") (files: $(printf '%s' "$changed" | tr '\n' ' '))"
204
+ TOOLING_REMOVED=$(( TOOLING_REMOVED + 1 ))
205
+ continue
206
+ fi
207
+ fi
208
+ printf '%s\n' "$draft" >> "$FILTERED_LIST"
209
+ done < "$BATCH_LIST"
210
+ mv "$FILTERED_LIST" "$BATCH_LIST"
211
+ BATCH_COUNT=$(wc -l < "$BATCH_LIST" | tr -d ' ')
212
+ if [ "$TOOLING_REMOVED" -gt 0 ]; then
213
+ log "after tooling-only filter: $BATCH_COUNT drafts remain (removed $TOOLING_REMOVED)"
214
+ fi
215
+ if [ "$BATCH_COUNT" -eq 0 ]; then
216
+ log "batch is empty after tooling-only filter — exit"
217
+ exit 0
218
+ fi
219
+ fi
220
+
123
221
  # Find clean ADR files at root of docs/adr/ (no `session:`).
124
222
  find "$ADR_DIR" -maxdepth 1 -type f -name '*.md' 2>/dev/null | while IFS= read -r f; do
125
223
  if ! is_draft "$f"; then
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.18.0] - 2026-05-25
8
+
9
+ ### Added
10
+
11
+ - `text`: `docs/adr/**` у канонічному `ignorePaths` правила `.cspell.json` (`policy/cspell/template/.cspell.json.snippet.json`). Машинно-генеровані ADR-документи більше не валідуються cspell-ом — це розриває петлю «правка `.cspell.json` → новий ADR-draft → знову `cspell` ламається на ньому». Локальні розширення `ignorePaths` лишаються дозволені (rego subset-of).
12
+ - `adr`: ENV `ADR_NORMALIZE_SKIP_TOOLING_ONLY` (default `1`) — вимикає structural skip у capture-/normalize-хуках. Документація в `adr.mdc` (таблиця ENV) і `skills/adr-normalize/SKILL.md`.
13
+
14
+ ### Changed
15
+
16
+ - `adr`: `.claude-template/hooks/capture-decisions.sh` — перед LLM-викликом перевіряє список `tool_use`-правок із transcript'у. Якщо всі правки у вузькому allowlist (`.cspell.json`, `docs/adr/*.md`, кореневі `AGENTS.md`/`CLAUDE.md`, `CHANGELOG.md`, `*/package.json` із diff виключно по ключу `version`) — `exit 0` із записом `skipping ADR capture: tooling-only session` у лог. Inline-функція `is_tooling_only_change` + `git_diff_only_version_field`, bash 3.2-сумісно.
17
+ - `adr`: `.claude-template/hooks/normalize-decisions.sh` — після формування батча для кожної чернетки читає `transcript:` із frontmatter і та сама перевірка allowlist'у. Tooling-only чернетки видаляються без виклику LLM; якщо батч порожній — `exit 0`.
18
+ - `text.mdc` 1.29 → 1.30: документація `docs/adr/**` у `ignorePaths`; приклади `.cspell.json` оновлено.
19
+ - `adr.mdc` 2.1 → 2.2: нова секція «Tooling-only skip» у Фазі 1, bullet у Фазі 2, рядок у таблиці ENV.
20
+
21
+ ### Notes
22
+
23
+ - Існуючі ENV (`ADR_NORMALIZE_THRESHOLD`, `…_MIN_INTERVAL_HOURS`, `…_BATCH`, `…_DRY`, recursion-guard `CAPTURE_DECISIONS_RUNNING` / `ADR_NORMALIZE_RUNNING`) поведінку не змінюють.
24
+ - `cspell.rego` subset-of-перевірку зберігає — нічого не зламано для проєктів, де користувач уже руками додав `docs/adr/**` у свій `.cspell.json`.
25
+
26
+ ## [1.17.4] - 2026-05-24
27
+
28
+ ### Changed
29
+
30
+ - Концерн `stryker_config`: gitignore-патерн `**/reports/stryker/.tmp/` + `**/reports/stryker/mutation.json` замінено на один broader `**/reports/stryker/` — увесь каталог Stryker-output-у. Покриває не лише `.tmp/` + `mutation.json`, а й HTML/dashboard-репорти якщо користувач додасть інші reporter-и. Існуючі дрібніші патерни в `.gitignore` користувача не видаляються (idempotent helper лише дописує), але стають надлишковими — користувач може почистити вручну за бажанням.
31
+ - `test.mdc` 2.1 → 2.2: оновлено опис gitignore-керування під новий broader patern.
32
+
7
33
  ## [1.17.3] - 2026-05-24
8
34
 
9
35
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.17.3",
3
+ "version": "1.18.0",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
package/rules/adr/adr.mdc CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Автоматичний збір ADR/Runbook/Knowledge-чернеток і батч-нормалізація у `docs/adr/` через Stop-хуки Claude Code та Cursor Agent
3
3
  alwaysApply: true
4
- version: '2.1'
4
+ version: '2.2'
5
5
  ---
6
6
 
7
7
  ## MADR v4 і дві фази
@@ -29,6 +29,8 @@ Stop-hook `capture-decisions.sh` зчитує JSONL-транскрипт сес
29
29
 
30
30
  Для Cursor payload скрипт бере `transcript_path`, `conversation_id` / `generation_id` і `workspace_roots[0]`; для Claude Code — `transcript_path`, `session_id` і `CLAUDE_PROJECT_DIR`.
31
31
 
32
+ **Tooling-only skip:** перед викликом LLM `capture-decisions.sh` дивиться у transcript на `tool_use`-правки (`Edit`/`Write`/`MultiEdit`). Якщо всі змінені файли потрапляють у вузький allowlist — `.cspell.json`, `docs/adr/*.md`, `CHANGELOG.md`, кореневі `AGENTS.md`/`CLAUDE.md`, або `package.json` з diff виключно по ключу `version` — хук виходить з `exit 0` без LLM-виклику. Це розриває петлю «`/n-lint` править `.cspell.json` → з'являється новий ADR-draft → наступний `/n-lint` знов псує правопис у цьому draft». Поведінку вимикає `ADR_NORMALIZE_SKIP_TOOLING_ONLY=0`.
33
+
32
34
  ### Фаза 2 — Normalize
33
35
 
34
36
  Stop-hook `normalize-decisions.sh` спрацьовує на тому самому `Stop`-евенті, але:
@@ -38,6 +40,7 @@ Stop-hook `normalize-decisions.sh` спрацьовує на тому самом
38
40
  - Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 10, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
39
41
  - Виходить миттєво, якщо репозиторій у стані `MERGE_HEAD` / `rebase-*` — небезпечно правити файли посеред конфлікту.
40
42
  - Виходить миттєво, якщо інший normalize-запуск тримає `flock` на `.claude/hooks/.normalize.lock` (тільки де `flock` доступний).
43
+ - Перед викликом LLM для кожної чернетки batch'а читає `transcript:` із frontmatter і той самий tool_use-список. Чернетки tooling-only — видаляє без виклику LLM. Якщо після фільтра batch порожній — `exit 0`.
41
44
 
42
45
  LLM повертає масив операцій:
43
46
 
@@ -65,6 +68,7 @@ LLM повертає масив операцій:
65
68
  | `ADR_NORMALIZE_DRY` | `0` | `1` — лише лог запланованих операцій, без змін на диску. |
66
69
  | `ADR_NORMALIZE_MODEL` | `sonnet` | Модель для `claude -p`. |
67
70
  | `ADR_NORMALIZE_CURSOR_MODEL` | `claude-4.6-sonnet-medium` | Модель для `cursor-agent -p`. |
71
+ | `ADR_NORMALIZE_SKIP_TOOLING_ONLY` | `1` | `0` — вимкнути structural skip tooling-only сесій (старий behavior). |
68
72
 
69
73
  Для ручного запуску (поза порогом і поза Stop-хуком) є **`/n-adr-normalize`** — slash-команда тимчасово виставляє `ADR_NORMALIZE_THRESHOLD=0` і `ADR_NORMALIZE_MIN_INTERVAL_HOURS=0` та викликає скрипт напряму.
70
74
 
@@ -23,11 +23,11 @@ import { resolveAllJsRoots } from '../../../scripts/utils/resolve-js-root.mjs'
23
23
  const HERE = dirname(fileURLToPath(import.meta.url))
24
24
  const BASELINE_PATH = join(HERE, 'data', 'stryker_config', 'stryker.config.baseline.mjs')
25
25
 
26
- // Stryker-output патерни для .gitignore: temp-каталог з backup-файлами
27
- // (`tempDirName: 'reports/stryker/.tmp'` у baseline) і JSON-репорт мутацій.
28
- // Канон in-place mode у baseline лишає backup'и у `reports/stryker/.tmp/backup-XXX/`,
29
- // які НЕ можна комітити. Подвійний-зірочка-префікс покриває всі workspaces.
30
- const STRYKER_GITIGNORE_ENTRIES = ['**/reports/stryker/.tmp/', '**/reports/stryker/mutation.json']
26
+ // Stryker-output патерн для .gitignore: увесь каталог reports/stryker/ — це
27
+ // build-артефакти (`tempDirName` backup'и, mutation.json, HTML/dashboard-репорти
28
+ // якщо користувач додасть інші reporter-и). Покриваємо одним патерном замість
29
+ // перелічування під-патернів. Подвійний-зірочка-префікс для monorepo workspaces.
30
+ const STRYKER_GITIGNORE_ENTRIES = ['**/reports/stryker/']
31
31
 
32
32
  /**
33
33
  * @returns {Promise<number>} 0 — OK або silently skipped, 1 — порушення
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: JS-тести (*.test.mjs) живуть у tests/. Правило `test` керує stryker.config.mjs (якщо js-lint enabled) і .cargo/mutants.toml (якщо rust enabled).
3
- version: '2.1'
3
+ version: '2.2'
4
4
  globs: "**/{.n-cursor.json,package.json,Cargo.toml,stryker.config.mjs,.cargo/mutants.toml},**/*.test.mjs"
5
5
  alwaysApply: false
6
6
  ---
@@ -82,4 +82,4 @@ Recursive globs ловлять файли всередині `tests/` так с
82
82
 
83
83
  Customization (mutate patterns, exclude rules, timeout) — відповідальність проєкту-споживача; концерни лише забезпечують наявність файлу як стартового baseline в кожному з виявлених workspace-каталогів.
84
84
 
85
- Додатково: коли `js-lint` enabled, концерн `stryker_config` ідемпотентно додає у кореневий `.gitignore` патерни Stryker-output-у — `**/reports/stryker/.tmp/` (in-place backup-каталог з baseline) і `**/reports/stryker/mutation.json` (JSON-репорт). Це запобігає випадковому коміту backup-копій вихідного коду та мутаційного звіту як build-артефактів.
85
+ Додатково: коли `js-lint` enabled, концерн `stryker_config` ідемпотентно додає у кореневий `.gitignore` патерн `**/reports/stryker/` увесь каталог Stryker-output(backup'и `tempDirName`, `mutation.json`, HTML/dashboard-репорти якщо додасте інші reporter-и). Це запобігає випадковому коміту build-артефактів.
@@ -7,6 +7,7 @@
7
7
  ".vscode",
8
8
  "report",
9
9
  "*.svg",
10
- "**/k8s/**/*.yaml"
10
+ "**/k8s/**/*.yaml",
11
+ "docs/adr/**"
11
12
  ]
12
13
  }
@@ -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.29'
4
+ version: '1.30'
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`**.
@@ -186,7 +186,7 @@ version: '1.29'
186
186
  {
187
187
  "version": "0.2",
188
188
  "language": "nitra",
189
- "ignorePaths": ["**/node_modules/**", "**/vscode-extension/**", "**/.git/**", ".vscode", "report", "*.svg", "**/k8s/**/*.yaml"],
189
+ "ignorePaths": ["**/node_modules/**", "**/vscode-extension/**", "**/.git/**", ".vscode", "report", "*.svg", "**/k8s/**/*.yaml", "docs/adr/**"],
190
190
  "import": ["@nitra/cspell-dict/cspell-ext.json"],
191
191
  "words": []
192
192
  }
@@ -194,6 +194,8 @@ version: '1.29'
194
194
 
195
195
  Канон базових ключів `.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).
196
196
 
197
+ `docs/adr/**` у канонічному `ignorePaths` — машинно-генеровані MADR-документи (драфти `capture-decisions.sh` + clean-ADR-и після `normalize-decisions.sh`). cspell-перевірка там безглузда: чернетка стирається наступним прогоном хук-ланцюжка, а будь-яка ручна правка правопису перезаписується. Локальні розширення `ignorePaths` дозволені — це лише мінімум.
198
+
197
199
  ```json title="package.json"
198
200
  {
199
201
  "scripts": {
@@ -230,7 +232,7 @@ version: '1.29'
230
232
  {
231
233
  "version": "0.2",
232
234
  "language": "en,uk,ru-ru,nitra",
233
- "ignorePaths": ["**/node_modules/**", "**/vscode-extension/**", "**/.git/**", ".vscode", "report", "*.svg", "**/k8s/**/*.yaml"],
235
+ "ignorePaths": ["**/node_modules/**", "**/vscode-extension/**", "**/.git/**", ".vscode", "report", "*.svg", "**/k8s/**/*.yaml", "docs/adr/**"],
234
236
  "import": ["@nitra/cspell-dict/cspell-ext.json"],
235
237
  "words": []
236
238
  }
@@ -62,9 +62,11 @@ description: >-
62
62
  - `ADR_NORMALIZE_BATCH=30` — більший батч (менше викликів LLM, більше токенів за раз).
63
63
  - `ADR_NORMALIZE_MODEL=opus` — інша модель `claude -p`.
64
64
  - `ADR_NORMALIZE_CURSOR_MODEL=…` — інша модель для cursor-agent fallback.
65
+ - `ADR_NORMALIZE_SKIP_TOOLING_ONLY=0` — вимкнути structural skip для tooling-only сесій (default `1`). Корисно лише якщо хочеш зберегти чернетки навіть для правок у `.cspell.json` / `CHANGELOG.md` / `version`-bump-ів.
65
66
 
66
67
  ## Якщо щось пішло не так
67
68
 
68
69
  - LLM повернув криву JSON → у логу буде `invalid JSON response (first 200 chars): …`. Запусти ще раз — нерідко це разовий збій.
69
70
  - Скрипт виходить миттєво без логу → перевір `ADR_NORMALIZE_RUNNING` у env (recursion guard) і чи репо не у стані merge/rebase.
70
71
  - Перейменування зробило дублі імен (`<timestamp>-<slug>-2.md`) → це нормально, скрипт детермінований; під час review можна обʼєднати руками й видалити `-2`.
72
+ - ADR-чернетки видаляються мовчки → це structural tooling-only skip. Перевір лог: `tail .claude/hooks/normalize-decisions.log | grep tooling-only`. Для діагностики на capture-стороні: `tail .claude/hooks/capture-decisions.log | grep tooling-only`. Аби тимчасово вимкнути — `ADR_NORMALIZE_SKIP_TOOLING_ONLY=0 bash .claude/hooks/normalize-decisions.sh`.