@nitra/cursor 1.17.4 → 1.18.1
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/capture-decisions.sh +79 -0
- package/.claude-template/hooks/normalize-decisions.sh +98 -0
- package/CHANGELOG.md +29 -0
- package/bin/rename-yaml-extensions.mjs +1 -1
- package/package.json +1 -1
- package/rules/abie/fix.mjs +1 -1
- package/rules/adr/adr.mdc +5 -1
- package/rules/adr/fix.mjs +1 -1
- package/rules/bun/fix.mjs +1 -1
- package/rules/capacitor/fix.mjs +1 -1
- package/rules/changelog/fix.mjs +1 -1
- package/rules/ci4/fix.mjs +1 -1
- package/rules/docker/fix.mjs +1 -1
- package/rules/docker/lint/lint.mjs +1 -1
- package/rules/efes/fix.mjs +1 -1
- package/rules/feedback/fix.mjs +1 -1
- package/rules/ga/fix.mjs +1 -1
- package/rules/graphql/fix.mjs +1 -1
- package/rules/hasura/fix.mjs +1 -1
- package/rules/image-avif/fix.mjs +1 -1
- package/rules/image-compress/fix.mjs +1 -1
- package/rules/js-bun-db/fix.mjs +1 -1
- package/rules/js-bun-redis/fix.mjs +1 -1
- package/rules/js-lint/fix.mjs +1 -1
- package/rules/js-mssql/fix.mjs +1 -1
- package/rules/js-run/fix.mjs +1 -1
- package/rules/k8s/fix.mjs +1 -1
- package/rules/k8s/lint/lint.mjs +1 -1
- package/rules/nginx-default-tpl/fix.mjs +1 -1
- package/rules/npm-module/fix.mjs +1 -1
- package/rules/php/fix.mjs +1 -1
- package/rules/php/lint/lint.mjs +1 -1
- package/rules/rego/fix.mjs +1 -1
- package/rules/rego/lint/lint.mjs +1 -1
- package/rules/rust/fix.mjs +1 -1
- package/rules/security/fix.mjs +1 -1
- package/rules/style-lint/fix.mjs +1 -1
- package/rules/tauri/fix.mjs +1 -1
- package/rules/test/fix.mjs +1 -1
- package/rules/text/fix.mjs +1 -1
- package/rules/text/lint/run-dotenv-linter.mjs +1 -1
- package/rules/text/lint/run-shellcheck.mjs +1 -1
- package/rules/text/lint/run-v8r.mjs +1 -1
- package/rules/text/policy/cspell/template/.cspell.json.snippet.json +2 -1
- package/rules/text/text.mdc +5 -3
- package/rules/vue/fix.mjs +1 -1
- package/scripts/cli-entry.mjs +19 -9
- package/scripts/lib/run-rule-cli.mjs +5 -11
- package/skills/adr-normalize/SKILL.md +2 -0
|
@@ -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,35 @@
|
|
|
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.1] - 2026-05-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`scripts/cli-entry.mjs::isRunAsCli`** + **`scripts/lib/run-rule-cli.mjs::isRunAsCli`** — функція приймала `()` без аргументів і всередині дивилася на власний `import.meta.url`, а не на caller'а. Через те, що `import.meta` лексично прив'язаний до файлу, де записаний, helper-функція ВСІГДА бачила свій файл — `cli-entry.mjs` / `run-rule-cli.mjs` — і ніколи не дорівнювала `process.argv[1]`. Результат: усі ~40 `if (isRunAsCli())` у `rules/<id>/fix.mjs` / `lint/*.mjs` / `bin/rename-yaml-extensions.mjs` ВСІГДА йшли в else-гілку, і `bun rules/<id>/fix.mjs` мовчки виходив `0` без жодного output'у. `npx @nitra/cursor fix <rule>` → `runFixCommand` → `spawnSync('bun', [fix.mjs])` → exit 0 без жодного reporter-звіту.
|
|
12
|
+
- **Fix:** функція тепер приймає `metaUrl` параметром: `isRunAsCli(import.meta.url)`. Реалізація через `realpathSync(fileURLToPath(metaUrl)) === realpathSync(resolve(process.argv[1]))` — `realpath` знімає різницю «symlink vs canonical» (macOS `/tmp` ↔ `/private/tmp`, pnpm content-addressable links, `node_modules/.bin/*` shim).
|
|
13
|
+
- **Консолідація:** `run-rule-cli.mjs::isRunAsCli` тепер `export { isRunAsCli } from '../cli-entry.mjs'` — одне джерело правди. Existing import paths у callers лишилися без змін.
|
|
14
|
+
- **Callsites:** всі ~40 викликів `isRunAsCli()` оновлено на `isRunAsCli(import.meta.url)`.
|
|
15
|
+
- **Tests:** додано три нові кейси у `scripts/tests/cli-entry.test.mjs` (entry-detection через spawn-fixture, symlink-нормалізація через `/tmp` → `/private/tmp`, no-arg fallback). Fixture — `scripts/tests/fixtures/cli-entry-as-cli.mjs`.
|
|
16
|
+
|
|
17
|
+
## [1.18.0] - 2026-05-25
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `text`: `docs/adr/**` у канонічному `ignorePaths` правила `.cspell.json` (`policy/cspell/template/.cspell.json.snippet.json`). Машинно-генеровані ADR-документи більше не валідуються cspell-ом — це розриває петлю «правка `.cspell.json` → новий ADR-draft → знову `cspell` ламається на ньому». Локальні розширення `ignorePaths` лишаються дозволені (rego subset-of).
|
|
22
|
+
- `adr`: ENV `ADR_NORMALIZE_SKIP_TOOLING_ONLY` (default `1`) — вимикає structural skip у capture-/normalize-хуках. Документація в `adr.mdc` (таблиця ENV) і `skills/adr-normalize/SKILL.md`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `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-сумісно.
|
|
27
|
+
- `adr`: `.claude-template/hooks/normalize-decisions.sh` — після формування батча для кожної чернетки читає `transcript:` із frontmatter і та сама перевірка allowlist'у. Tooling-only чернетки видаляються без виклику LLM; якщо батч порожній — `exit 0`.
|
|
28
|
+
- `text.mdc` 1.29 → 1.30: документація `docs/adr/**` у `ignorePaths`; приклади `.cspell.json` оновлено.
|
|
29
|
+
- `adr.mdc` 2.1 → 2.2: нова секція «Tooling-only skip» у Фазі 1, bullet у Фазі 2, рядок у таблиці ENV.
|
|
30
|
+
|
|
31
|
+
### Notes
|
|
32
|
+
|
|
33
|
+
- Існуючі ENV (`ADR_NORMALIZE_THRESHOLD`, `…_MIN_INTERVAL_HOURS`, `…_BATCH`, `…_DRY`, recursion-guard `CAPTURE_DECISIONS_RUNNING` / `ADR_NORMALIZE_RUNNING`) поведінку не змінюють.
|
|
34
|
+
- `cspell.rego` subset-of-перевірку зберігає — нічого не зламано для проєктів, де користувач уже руками додав `docs/adr/**` у свій `.cspell.json`.
|
|
35
|
+
|
|
7
36
|
## [1.17.4] - 2026-05-24
|
|
8
37
|
|
|
9
38
|
### Changed
|
|
@@ -34,7 +34,7 @@ export async function runRenameYamlExtensionsCli(argv) {
|
|
|
34
34
|
return errors.length > 0 ? 1 : 0
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
if (isRunAsCli()) {
|
|
37
|
+
if (isRunAsCli(import.meta.url)) {
|
|
38
38
|
const code = await runRenameYamlExtensionsCli(process.argv.slice(2))
|
|
39
39
|
if (code !== 0) {
|
|
40
40
|
process.exitCode = 1
|
package/package.json
CHANGED
package/rules/abie/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
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.
|
|
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
|
|
package/rules/adr/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/bun/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/capacitor/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/changelog/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/ci4/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/docker/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/efes/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/feedback/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/ga/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/graphql/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/hasura/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/image-avif/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/js-bun-db/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/js-lint/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/js-mssql/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/js-run/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/k8s/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/k8s/lint/lint.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/npm-module/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/php/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/php/lint/lint.mjs
CHANGED
package/rules/rego/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/rego/lint/lint.mjs
CHANGED
|
@@ -143,6 +143,6 @@ export function runLintRegoSteps(cwd = process.cwd()) {
|
|
|
143
143
|
*/
|
|
144
144
|
export const runLintRego = () => runStandardLint(import.meta.dirname, () => runLintRegoSteps())
|
|
145
145
|
|
|
146
|
-
if (isRunAsCli()) {
|
|
146
|
+
if (isRunAsCli(import.meta.url)) {
|
|
147
147
|
process.exitCode = await runLintRego()
|
|
148
148
|
}
|
package/rules/rust/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/security/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/style-lint/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/tauri/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/test/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/rules/text/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
|
@@ -76,7 +76,7 @@ export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS) {
|
|
|
76
76
|
return 0
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
if (isRunAsCli()) {
|
|
79
|
+
if (isRunAsCli(import.meta.url)) {
|
|
80
80
|
const globs = process.argv.length > 2 ? process.argv.slice(2) : DEFAULT_V8R_GLOBS
|
|
81
81
|
process.exitCode = runV8rWithGlobs(globs)
|
|
82
82
|
}
|
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.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
|
}
|
package/rules/vue/fix.mjs
CHANGED
|
@@ -11,7 +11,7 @@ export function run(ctx) {
|
|
|
11
11
|
return runStandardRule(import.meta.dirname, ctx)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
if (isRunAsCli()) {
|
|
14
|
+
if (isRunAsCli(import.meta.url)) {
|
|
15
15
|
// Standalone: bun rules/<id>/fix.mjs — повний еквівалент `npx @nitra/cursor fix <id>`
|
|
16
16
|
// (config-loading + whitelist + summary). Дві ролі fix.mjs: library (run) + standalone (main).
|
|
17
17
|
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- standalone entry-point має повертати exit-code для CI/IDE
|
package/scripts/cli-entry.mjs
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Визначення, чи виконується поточний ESM-модуль як точка входу CLI, а не як import у тестах чи інших модулях.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Прийом: модуль, що хоче знати свій статус, передає `import.meta.url` —
|
|
5
|
+
* `import.meta` лексично прив'язаний до файлу, де він записаний, тому helper-функція
|
|
6
|
+
* без аргументу неминуче дивилася б на свій файл, а не на caller. `realpathSync` на обох
|
|
7
|
+
* сторонах знімає різницю «symlink vs canonical» (macOS `/tmp` ↔ `/private/tmp`,
|
|
8
|
+
* `node_modules/.bin/*` shim, pnpm-style content-addressable links).
|
|
6
9
|
*/
|
|
10
|
+
import { realpathSync } from 'node:fs'
|
|
7
11
|
import { resolve } from 'node:path'
|
|
8
12
|
import { fileURLToPath } from 'node:url'
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
15
|
* Чи виконується модуль як точка входу CLI (прямий запуск), а не через import.
|
|
12
|
-
* @
|
|
16
|
+
* @param {string | URL} [metaUrl] `import.meta.url` модуля-caller'а. Без нього — завжди `false`.
|
|
17
|
+
* @returns {boolean} `true`, якщо файл, з якого передано `metaUrl`, є `process.argv[1]`.
|
|
13
18
|
*/
|
|
14
|
-
export function isRunAsCli() {
|
|
19
|
+
export function isRunAsCli(metaUrl) {
|
|
20
|
+
if (!metaUrl) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
const entry = process.argv[1]
|
|
24
|
+
if (!entry) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
15
27
|
try {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
return fileURLToPath(import.meta.url) === resolve(entry)
|
|
28
|
+
const callerPath = realpathSync(fileURLToPath(metaUrl))
|
|
29
|
+
const entryPath = realpathSync(resolve(entry))
|
|
30
|
+
return callerPath === entryPath
|
|
21
31
|
} catch {
|
|
22
32
|
return false
|
|
23
33
|
}
|
|
@@ -7,23 +7,17 @@
|
|
|
7
7
|
* Library-mode виклик з CLI orchestration — інше: див. `runStandardRule` + `fix.mjs::run(ctx)`.
|
|
8
8
|
*/
|
|
9
9
|
import { basename } from 'node:path'
|
|
10
|
-
import { pathToFileURL } from 'node:url'
|
|
11
10
|
|
|
12
11
|
import { isRuleEnabled, readNCursorConfigLite } from './read-n-cursor-config-lite.mjs'
|
|
13
12
|
import { runStandardRule } from './run-standard-rule.mjs'
|
|
14
13
|
import { getOrCreateWalkCache } from '../utils/walk-cache.mjs'
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
// Re-export для зворотної сумісності: уся `rules/<id>/fix.mjs` уже імпортує `isRunAsCli`
|
|
16
|
+
// саме звідси. Канонічна реалізація — у `scripts/cli-entry.mjs`. Caller передає
|
|
17
|
+
// `import.meta.url`: `if (isRunAsCli(import.meta.url)) …`.
|
|
18
|
+
export { isRunAsCli } from '../cli-entry.mjs'
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
* Чи поточний модуль запущено як CLI entry-point (`bun rules/<id>/fix.mjs`).
|
|
20
|
-
* @returns {boolean} true, коли `import.meta.url` збігається з `process.argv[1]`
|
|
21
|
-
*/
|
|
22
|
-
export function isRunAsCli() {
|
|
23
|
-
const entry = process.argv[1]
|
|
24
|
-
if (!entry) return false
|
|
25
|
-
return import.meta.url === pathToFileURL(entry).href
|
|
26
|
-
}
|
|
20
|
+
const PACKAGE_NAME = '@nitra/cursor'
|
|
27
21
|
|
|
28
22
|
/**
|
|
29
23
|
* @param {string} ruleDir абсолютний шлях до `rules/<id>/`
|
|
@@ -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`.
|