@7n/rules 1.3.0 → 1.3.2
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/CHANGELOG.md +12 -0
- package/docs/index.md +0 -2
- package/docs/vitest.config.md +1 -1
- package/package.json +1 -1
- package/rules/abie/clean_merged_ignore_branches/docs/fix-clean_merged_ignore_branches.md +31 -0
- package/rules/abie/clean_merged_ignore_branches/docs/index.md +9 -0
- package/rules/abie/clean_merged_ignore_branches/fix-clean_merged_ignore_branches.mjs +6 -1
- package/rules/docker/lint_docker_yml/docs/fix-lint_docker_yml.md +31 -0
- package/rules/docker/lint_docker_yml/docs/index.md +9 -0
- package/rules/docker/lint_docker_yml/fix-lint_docker_yml.mjs +3 -1
- package/rules/ga/clean_ga_workflows/docs/fix-clean_ga_workflows.md +33 -0
- package/rules/ga/clean_ga_workflows/docs/index.md +9 -0
- package/rules/ga/clean_ga_workflows/fix-clean_ga_workflows.mjs +6 -1
- package/rules/ga/clean_merged_branch/docs/fix-clean_merged_branch.md +27 -0
- package/rules/ga/clean_merged_branch/docs/index.md +9 -0
- package/rules/ga/clean_merged_branch/fix-clean_merged_branch.mjs +6 -1
- package/rules/ga/git_ai/docs/fix-git_ai.md +30 -0
- package/rules/ga/git_ai/docs/index.md +9 -0
- package/rules/ga/git_ai/fix-git_ai.mjs +3 -1
- package/rules/ga/lint_ga/docs/fix-lint_ga.md +32 -0
- package/rules/ga/lint_ga/docs/index.md +9 -0
- package/rules/ga/lint_ga/fix-lint_ga.mjs +3 -1
- package/rules/ga/vscode_settings/docs/fix-vscode_settings.md +26 -0
- package/rules/ga/vscode_settings/docs/index.md +9 -0
- package/rules/ga/vscode_settings/fix-vscode_settings.mjs +3 -1
- package/rules/ga/zizmor_yml/docs/fix-zizmor_yml.md +28 -0
- package/rules/ga/zizmor_yml/docs/index.md +9 -0
- package/rules/js/jscpd_config/docs/fix-jscpd_config.md +26 -0
- package/rules/js/jscpd_config/docs/index.md +9 -0
- package/rules/js/lint_js_yml/docs/fix-lint_js_yml.md +28 -0
- package/rules/js/lint_js_yml/docs/index.md +9 -0
- package/rules/js/package_json/docs/fix-package_json.md +26 -0
- package/rules/js/package_json/docs/index.md +9 -0
- package/rules/k8s/lint_k8s_yml/docs/fix-lint_k8s_yml.md +30 -0
- package/rules/k8s/lint_k8s_yml/docs/index.md +9 -0
- package/rules/k8s/lint_k8s_yml/fix-lint_k8s_yml.mjs +3 -1
- package/rules/npm-module/emit_types_config/docs/fix-emit_types_config.md +25 -0
- package/rules/npm-module/emit_types_config/docs/index.md +9 -0
- package/rules/npm-module/emit_types_config/fix-emit_types_config.mjs +3 -1
- package/rules/npm-module/npm_package_json/docs/fix-npm_package_json.md +27 -0
- package/rules/npm-module/npm_package_json/docs/index.md +9 -0
- package/rules/npm-module/npm_package_json/fix-npm_package_json.mjs +3 -1
- package/rules/npm-module/npm_publish_yml/docs/fix-npm_publish_yml.md +28 -0
- package/rules/npm-module/npm_publish_yml/docs/index.md +9 -0
- package/rules/npm-module/npm_publish_yml/fix-npm_publish_yml.mjs +4 -1
- package/rules/npm-module/root_package_json/docs/fix-root_package_json.md +26 -0
- package/rules/npm-module/root_package_json/docs/index.md +9 -0
- package/rules/npm-module/root_package_json/fix-root_package_json.mjs +3 -1
- package/rules/php/lint_php_yml/docs/fix-lint_php_yml.md +28 -0
- package/rules/php/lint_php_yml/docs/index.md +9 -0
- package/rules/php/lint_php_yml/fix-lint_php_yml.mjs +3 -1
- package/rules/python/lint_python_yml/docs/fix-lint_python_yml.md +27 -0
- package/rules/python/lint_python_yml/docs/index.md +9 -0
- package/rules/python/lint_python_yml/fix-lint_python_yml.mjs +3 -1
- package/rules/rego/vscode_settings/docs/fix-vscode_settings.md +27 -0
- package/rules/rego/vscode_settings/docs/index.md +9 -0
- package/rules/rego/vscode_settings/fix-vscode_settings.mjs +3 -1
- package/rules/rust/lint_rust_yml/docs/fix-lint_rust_yml.md +28 -0
- package/rules/rust/lint_rust_yml/docs/index.md +9 -0
- package/rules/security/lint_security_yml/docs/fix-lint_security_yml.md +31 -0
- package/rules/security/lint_security_yml/docs/index.md +9 -0
- package/rules/security/lint_security_yml/fix-lint_security_yml.mjs +6 -1
- package/rules/style/lint_style_yml/docs/fix-lint_style_yml.md +31 -0
- package/rules/style/lint_style_yml/docs/index.md +9 -0
- package/rules/style/lint_style_yml/fix-lint_style_yml.mjs +3 -1
- package/rules/style/package_json/docs/fix-package_json.md +27 -0
- package/rules/style/package_json/docs/index.md +9 -0
- package/rules/style/vscode_settings/docs/fix-vscode_settings.md +27 -0
- package/rules/style/vscode_settings/docs/index.md +9 -0
- package/rules/style/vscode_settings/fix-vscode_settings.mjs +3 -1
- package/rules/text/lint_text/docs/fix-lint_text.md +28 -0
- package/rules/text/lint_text/docs/index.md +9 -0
- package/rules/text/oxfmtrc/docs/fix-oxfmtrc.md +26 -0
- package/rules/text/oxfmtrc/docs/index.md +9 -0
- package/rules/text/vscode_settings/docs/fix-vscode_settings.md +27 -0
- package/rules/text/vscode_settings/docs/index.md +9 -0
- package/rules/text/vscode_settings/fix-vscode_settings.mjs +3 -1
- package/rules/worktree/vscode_settings/docs/fix-vscode_settings.md +28 -0
- package/rules/worktree/vscode_settings/docs/index.md +9 -0
- package/rules/worktree/zed_settings/docs/fix-zed_settings.md +28 -0
- package/rules/worktree/zed_settings/docs/index.md +9 -0
- package/rules/worktree/zed_settings/fix-zed_settings.mjs +3 -1
- package/scripts/docs/skills-cli.md +1 -1
- package/scripts/lib/fix/docs/index.md +4 -5
- package/scripts/lib/fix/docs/template-deep-merge.md +35 -0
- package/scripts/lib/fix/template-deep-merge.mjs +58 -47
- package/scripts/skills-cli.mjs +15 -4
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-vscode_settings.mjs
|
|
4
|
+
resource: npm/rules/text/vscode_settings/fix-vscode_settings.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: aa3c10a6
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.93
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Файл визначає, за якими правилами `patterns` перевіряє відповідність `.vscode/settings.json` конфігурації з `settings.json`. Це потрібно, щоб підтримувати узгоджений стан редакторських налаштувань у проєкті.
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
1. `patterns` формує набір правил для приведення `.vscode/settings.json` до узгодженого стану з шаблоном.
|
|
21
|
+
2. `patterns` орієнтується на конфігурацію `settings.json` як на джерело очікуваного вигляду налаштувань.
|
|
22
|
+
3. `patterns` потрібен, щоб автоматично підтримувати єдину структуру редакторських налаштувань у проєкті.
|
|
23
|
+
4. `patterns` працює лише як опис правил і не виконує запис у файлову систему чи базу даних.
|
|
24
|
+
|
|
25
|
+
## Гарантії поведінки
|
|
26
|
+
|
|
27
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/text/vscode_settings
|
|
4
|
+
resource: npm/rules/text/vscode_settings/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------------------------- | --------- |
|
|
9
|
+
| [fix-vscode_settings.mjs](fix-vscode_settings.md) | JS Module |
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { createTemplateFixPattern } from '../../../scripts/lib/fix/template-deep-merge.mjs'
|
|
2
2
|
|
|
3
|
-
export const patterns = [
|
|
3
|
+
export const patterns = [
|
|
4
|
+
createTemplateFixPattern({ id: 'text-vscode_settings-template', targetPath: '.vscode/settings.json' })
|
|
5
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-vscode_settings.mjs
|
|
4
|
+
resource: npm/rules/worktree/vscode_settings/fix-vscode_settings.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: e68a8ed3
|
|
7
|
+
model: openai-codex/gpt-5.5
|
|
8
|
+
tier: cloud-avg
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.97
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Надає `patterns` для визначення конфігураційних файлів, на які має зважати перевірка робочого дерева. Спирається на конфіги: settings.json.
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
1. `patterns` визначає правило виправлення для робочого дерева, яке підтримує наявність очікуваного VS Code-конфігу `settings.json`.
|
|
21
|
+
|
|
22
|
+
2. `patterns` спрямовує перевірку на `.vscode/settings.json`, щоб проєкт мав узгоджені налаштування редактора для всіх учасників.
|
|
23
|
+
|
|
24
|
+
3. `patterns` не змінює файлову систему самостійно; воно лише описує доступний шаблон виправлення для зовнішнього механізму застосування правил.
|
|
25
|
+
|
|
26
|
+
## Гарантії поведінки
|
|
27
|
+
|
|
28
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/worktree/vscode_settings
|
|
4
|
+
resource: npm/rules/worktree/vscode_settings/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------------------------- | --------- |
|
|
9
|
+
| [fix-vscode_settings.mjs](fix-vscode_settings.md) | JS Module |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: fix-zed_settings.mjs
|
|
4
|
+
resource: npm/rules/worktree/zed_settings/fix-zed_settings.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 452faa10
|
|
7
|
+
model: openai-codex/gpt-5.5
|
|
8
|
+
tier: cloud-avg
|
|
9
|
+
score: 100
|
|
10
|
+
issues: judge:inaccurate:0.98
|
|
11
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Огляд
|
|
15
|
+
|
|
16
|
+
Надає правило `patterns` для перевірки очікуваних налаштувань проєкту. Код спирається на конфіг `settings.json` і працює read-only, щоб описати вимоги до конфігурації без самостійного внесення змін.
|
|
17
|
+
|
|
18
|
+
## Поведінка
|
|
19
|
+
|
|
20
|
+
1. `patterns` визначає правило автоматичного приведення робочого дерева до очікуваного шаблону налаштувань Zed.
|
|
21
|
+
|
|
22
|
+
2. `patterns` орієнтується на конфіг `settings.json`, щоб забезпечити наявність і узгодженість `.zed/settings.json` у проєкті.
|
|
23
|
+
|
|
24
|
+
3. `patterns` не змінює файлову систему самостійно; воно лише описує поведінку виправлення для зовнішнього механізму застосування правил.
|
|
25
|
+
|
|
26
|
+
## Гарантії поведінки
|
|
27
|
+
|
|
28
|
+
- Read-only: не виконує операцій запису (ФС/БД).
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { createTemplateFixPattern } from '../../../scripts/lib/fix/template-deep-merge.mjs'
|
|
2
2
|
|
|
3
|
-
export const patterns = [
|
|
3
|
+
export const patterns = [
|
|
4
|
+
createTemplateFixPattern({ id: 'worktree-zed_settings-template', targetPath: '.zed/settings.json' })
|
|
5
|
+
]
|
|
@@ -4,8 +4,7 @@ title: npm/scripts/lib/fix
|
|
|
4
4
|
resource: npm/scripts/lib/fix/
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
|
10
|
-
|
|
|
11
|
-
| [vscode-ext-add.mjs](vscode-ext-add.md) | JS Module |
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------------------------- | --------- |
|
|
9
|
+
| [template-deep-merge.mjs](template-deep-merge.md) | JS Module |
|
|
10
|
+
| [vscode-ext-add.mjs](vscode-ext-add.md) | JS Module |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: JS Module
|
|
3
|
+
title: template-deep-merge.mjs
|
|
4
|
+
resource: npm/scripts/lib/fix/template-deep-merge.mjs
|
|
5
|
+
docgen:
|
|
6
|
+
crc: 90221a92
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
score: 100
|
|
9
|
+
issues: judge:inaccurate:0.98
|
|
10
|
+
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Файл створює T0-патерн для одного target-файлу за канонічним `template/*.snippet.{json,jsonc,yml,yaml}` свого concern-а, який резолвиться через `ctx.concernDir`, щоб уніфіковано приводити `engine:"template"` і `engine:"rego"` до потрібного стану без ручного редагування. Для merge об’єкти поєднуються по ключах, масиви зводяться до union за структурним підмножинним збігом, а leaf-значення замінюються канонічним значенням із snippet; якщо target-файл відсутній, snippet копіюється без merge. Для JSON/JSONC застосовується plain-object merge з `JSON.stringify`, а для YAML — `Document API` (`setIn`/`addIn`/`hasIn`), щоб зберігати наявні коментарі й форматування та створювати лише те, чого бракує. `createTemplateFixPattern` працює fail-safe: не кидає винятків назовні й за відсутності потрібного snippet або некоректного входу повертає порожнє значення.
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
1. `createTemplateFixPattern` формує T0-патерн для одного цільового файлу, який приводить його до стану, описаного канонічним snippet-файлом у `template/` свого concern-а.
|
|
20
|
+
2. Під час запуску патерн спершу визначає, чи стосується порушення саме цього цільового файлу; якщо ні — нічого не змінює.
|
|
21
|
+
3. Далі він шукає відповідний snippet-файл для цільового файлу; якщо snippet не знайдено або немає контексту concern-а, зміни не виконуються.
|
|
22
|
+
4. Якщо цільового файлу ще немає, він створює його з вмісту snippet без merge — як початковий канонічний стан.
|
|
23
|
+
5. Якщо цільовий файл уже існує, патерн зливає snippet у наявний вміст: об’єкти доповнюються по ключах, масиви поповнюються лише відсутніми структурно елементами, а прості значення замінюються канонічними.
|
|
24
|
+
6. Для JSON і JSONC результат записується як нормалізований JSON-текст; якщо вхід невалідний або файл уже відповідає snippet-у, він лишається без змін.
|
|
25
|
+
7. Для YAML збереження робиться так, щоб не втрачати коментарі й форматування; якщо YAML невалідний, або вже відповідає snippet-у, файл не чіпається.
|
|
26
|
+
8. У разі успішного оновлення патерн фіксує запис і повідомляє, що цільовий файл приведено у відповідність snippet.
|
|
27
|
+
|
|
28
|
+
## Публічний API
|
|
29
|
+
|
|
30
|
+
- createTemplateFixPattern — Формує T0-патерн, який вирівнює `targetPath` під `template/*.snippet.*` для свого concern-а; працює як deep-merge, не ламає повторні запуски і лишає одного writer-а для single-target snippet-концернів з `engine:"template"` або `engine:"rego"` на тому самому snippet-шаблоні.
|
|
31
|
+
|
|
32
|
+
## Гарантії поведінки
|
|
33
|
+
|
|
34
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
35
|
+
- За певних помилок повертає порожнє значення (напр. `null`) замість винятку.
|
|
@@ -25,7 +25,7 @@ const SNIPPET_EXTS = ['yml', 'yaml', 'json', 'jsonc']
|
|
|
25
25
|
* Чи `needle` структурно вже присутній у якомусь елементі `actualArray`
|
|
26
26
|
* (та сама subset-семантика, що й у детекторі — жодного окремого визначення "збігу").
|
|
27
27
|
* @param {unknown[]} actualArray наявний масив
|
|
28
|
-
* @param {unknown} needle елемент
|
|
28
|
+
* @param {unknown} needle елемент snippet-а, наявність якого перевіряємо
|
|
29
29
|
* @returns {boolean} true — вже присутній структурно
|
|
30
30
|
*/
|
|
31
31
|
function containedIn(actualArray, needle) {
|
|
@@ -98,6 +98,57 @@ function mergeYamlDoc(doc, snippet, path) {
|
|
|
98
98
|
doc.setIn(path, snippet) // leaf — перезаписуємо канонічним значенням
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Рахує наступний вміст JSON/JSONC-target. `null` — невалідний вхід (не чіпаємо);
|
|
103
|
+
* незмінений `prevText` — уже відповідає snippet-у (idempotent, без reformat).
|
|
104
|
+
* @param {string} prevText наявний вміст target-файлу
|
|
105
|
+
* @param {string} snippetPath абсолютний шлях до snippet-файлу
|
|
106
|
+
* @returns {string|null} новий вміст, незмінений `prevText`, або `null`
|
|
107
|
+
*/
|
|
108
|
+
function computeJsonNextText(prevText, snippetPath) {
|
|
109
|
+
let snippet
|
|
110
|
+
let actual
|
|
111
|
+
try {
|
|
112
|
+
snippet = JSON.parse(readFileSync(snippetPath, 'utf8'))
|
|
113
|
+
actual = JSON.parse(prevText)
|
|
114
|
+
} catch {
|
|
115
|
+
return null // невалідний JSON — не чіпаємо детермінованим фіксом
|
|
116
|
+
}
|
|
117
|
+
// Уже відповідає snippet-у (та сама перевірка, що й детектор) → без reformat:
|
|
118
|
+
// JSON.stringify інакше переформатував би вже коректний файл (напр. компактний
|
|
119
|
+
// однорядковий snippet → pretty-print) без жодної реальної зміни.
|
|
120
|
+
if (checkSnippet(actual, snippet, { targetPath: '', source: '' }).length === 0) return prevText
|
|
121
|
+
return JSON.stringify(mergeJsonValue(actual, snippet), null, 2) + '\n'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Рахує наступний вміст YAML-target через `yaml` Document API. `null` — невалідний
|
|
126
|
+
* вхід; незмінений `prevText` — уже відповідає snippet-у (idempotent, без reformat).
|
|
127
|
+
* @param {string} prevText наявний вміст target-файлу
|
|
128
|
+
* @param {string} snippetPath абсолютний шлях до snippet-файлу
|
|
129
|
+
* @returns {Promise<string|null>} новий вміст, незмінений `prevText`, або `null`
|
|
130
|
+
*/
|
|
131
|
+
async function computeYamlNextText(prevText, snippetPath) {
|
|
132
|
+
const { parse, parseDocument } = await import('yaml')
|
|
133
|
+
let snippet
|
|
134
|
+
let actualPlain
|
|
135
|
+
try {
|
|
136
|
+
snippet = parse(readFileSync(snippetPath, 'utf8'))
|
|
137
|
+
actualPlain = parse(prevText)
|
|
138
|
+
} catch {
|
|
139
|
+
return null // невалідний YAML — не чіпаємо детермінованим фіксом
|
|
140
|
+
}
|
|
141
|
+
// Уже відповідає snippet-у → без reformat: Document.toString() не завжди
|
|
142
|
+
// byte-identical на вже коректному вмісті (напр. folded block scalars).
|
|
143
|
+
if (checkSnippet(actualPlain, snippet, { targetPath: '', source: '' }).length === 0) return prevText
|
|
144
|
+
const doc = parseDocument(prevText)
|
|
145
|
+
if (doc.errors.length > 0) return null
|
|
146
|
+
if (snippet !== null && typeof snippet === 'object' && !Array.isArray(snippet)) {
|
|
147
|
+
for (const [k, v] of Object.entries(snippet)) mergeYamlDoc(doc, v, [k])
|
|
148
|
+
}
|
|
149
|
+
return doc.toString()
|
|
150
|
+
}
|
|
151
|
+
|
|
101
152
|
/**
|
|
102
153
|
* Створює T0-патерн, що приводить `targetPath` у відповідність `template/*.snippet.*`
|
|
103
154
|
* свого concern-а (deep-merge, idempotent). Один writer — для будь-якого single-target
|
|
@@ -110,16 +161,13 @@ export function createTemplateFixPattern({ id, targetPath }) {
|
|
|
110
161
|
id,
|
|
111
162
|
test: violations => violations.some(v => v.file === targetPath),
|
|
112
163
|
apply: async (violations, ctx) => {
|
|
113
|
-
if (
|
|
164
|
+
if (violations.every(v => v.file !== targetPath)) return { touchedFiles: [] }
|
|
114
165
|
if (!ctx.concernDir) return { touchedFiles: [] }
|
|
115
166
|
|
|
116
167
|
const snippetPath = findSnippetFile(join(ctx.concernDir, 'template'), basename(targetPath))
|
|
117
168
|
if (!snippetPath) return { touchedFiles: [] }
|
|
118
169
|
|
|
119
170
|
const absTarget = join(ctx.cwd, targetPath)
|
|
120
|
-
const ext = extname(snippetPath).toLowerCase()
|
|
121
|
-
const isJson = ext === '.json' || ext === '.jsonc'
|
|
122
|
-
|
|
123
171
|
const prevText = existsSync(absTarget) ? readFileSync(absTarget, 'utf8') : null
|
|
124
172
|
|
|
125
173
|
// Файл відсутній → копіюємо snippet як є (без merge — немає з чим мерджити).
|
|
@@ -131,49 +179,12 @@ export function createTemplateFixPattern({ id, targetPath }) {
|
|
|
131
179
|
return { touchedFiles: [absTarget], message: `${targetPath}: створено зі snippet` }
|
|
132
180
|
}
|
|
133
181
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
snippet = JSON.parse(readFileSync(snippetPath, 'utf8'))
|
|
140
|
-
actual = JSON.parse(prevText)
|
|
141
|
-
} catch {
|
|
142
|
-
return { touchedFiles: [] } // невалідний JSON — не чіпаємо детермінованим фіксом
|
|
143
|
-
}
|
|
144
|
-
// Уже відповідає snippet-у (та сама перевірка, що й детектор) → не чіпаємо файл
|
|
145
|
-
// взагалі: без цієї гейтки JSON.stringify переформатував би вже коректний файл
|
|
146
|
-
// (напр. компактний однорядковий snippet → pretty-print) без жодної реальної зміни.
|
|
147
|
-
if (checkSnippet(actual, snippet, { targetPath: '', source: '' }).length === 0) {
|
|
148
|
-
return { touchedFiles: [] }
|
|
149
|
-
}
|
|
150
|
-
const merged = mergeJsonValue(actual, snippet)
|
|
151
|
-
nextText = JSON.stringify(merged, null, 2) + '\n'
|
|
152
|
-
} else {
|
|
153
|
-
const { parse, parseDocument } = await import('yaml')
|
|
154
|
-
let snippet
|
|
155
|
-
let actualPlain
|
|
156
|
-
try {
|
|
157
|
-
snippet = parse(readFileSync(snippetPath, 'utf8'))
|
|
158
|
-
actualPlain = parse(prevText)
|
|
159
|
-
} catch {
|
|
160
|
-
return { touchedFiles: [] } // невалідний YAML — не чіпаємо детермінованим фіксом
|
|
161
|
-
}
|
|
162
|
-
// Уже відповідає snippet-у (та сама перевірка, що й детектор) → не чіпаємо файл
|
|
163
|
-
// взагалі: Document.toString() не завжди byte-identical на вже коректному вмісті
|
|
164
|
-
// (напр. folded block scalars переформатовуються при round-trip без потреби).
|
|
165
|
-
if (checkSnippet(actualPlain, snippet, { targetPath: '', source: '' }).length === 0) {
|
|
166
|
-
return { touchedFiles: [] }
|
|
167
|
-
}
|
|
168
|
-
const doc = parseDocument(prevText)
|
|
169
|
-
if (doc.errors.length > 0) return { touchedFiles: [] }
|
|
170
|
-
if (snippet !== null && typeof snippet === 'object' && !Array.isArray(snippet)) {
|
|
171
|
-
for (const [k, v] of Object.entries(snippet)) mergeYamlDoc(doc, v, [k])
|
|
172
|
-
}
|
|
173
|
-
nextText = doc.toString()
|
|
174
|
-
}
|
|
182
|
+
const isJson = ['.json', '.jsonc'].includes(extname(snippetPath).toLowerCase())
|
|
183
|
+
const nextText = isJson
|
|
184
|
+
? computeJsonNextText(prevText, snippetPath)
|
|
185
|
+
: await computeYamlNextText(prevText, snippetPath)
|
|
175
186
|
|
|
176
|
-
if (nextText === prevText) return { touchedFiles: [] }
|
|
187
|
+
if (nextText === null || nextText === prevText) return { touchedFiles: [] }
|
|
177
188
|
ctx.recordWrite?.(absTarget)
|
|
178
189
|
writeFileSync(absTarget, nextText, 'utf8')
|
|
179
190
|
return { touchedFiles: [absTarget], message: `${targetPath}: приведено у відповідність snippet` }
|
package/scripts/skills-cli.mjs
CHANGED
|
@@ -22,7 +22,7 @@ import { dirname, join } from 'node:path'
|
|
|
22
22
|
import { cwd } from 'node:process'
|
|
23
23
|
import { fileURLToPath } from 'node:url'
|
|
24
24
|
|
|
25
|
-
import { readSkillMetaRaw, skillTier } from './lib/skill-meta.mjs'
|
|
25
|
+
import { readSkillMetaRaw, skillRequiresRoot, skillTier } from './lib/skill-meta.mjs'
|
|
26
26
|
|
|
27
27
|
/** Виконавці скіла. `pi` — вбудований (рекомендований); `cursor`/`codex`/`claude` — зовнішні CLI (`claude` — deprecated). */
|
|
28
28
|
const RUNNERS = new Set(['pi', 'cursor', 'codex', 'claude'])
|
|
@@ -177,13 +177,20 @@ async function runPiRunner(prompt, rawSkillName, skillsRoot, projectDir, logErro
|
|
|
177
177
|
* або deprecated `claude -p`) — промпт передається через stdin. `claude` лишається як
|
|
178
178
|
* deprecated fallback для тих, у кого pi-модель ще не налаштована; буде прибрано
|
|
179
179
|
* (мігруй на `skill pi`). `cursor`/`codex` — повноцінні альтернативні раннери.
|
|
180
|
+
*
|
|
181
|
+
* `cursor-agent` у non-interactive режимі блокується власним `approvalMode: "allowlist"`
|
|
182
|
+
* користувача (git/bun/npx/rm тощо потребують інтерактивного підтвердження, якого в headless
|
|
183
|
+
* виклику немає). Для `worktree:true`/`requireRoot:true` скілів (блок-радіус вже ізольований)
|
|
184
|
+
* додаємо `--force`, замість того щоб просити користувача розширювати глобальний allowlist
|
|
185
|
+
* у `~/.cursor/cli-config.json`.
|
|
180
186
|
* @param {'claude' | 'cursor' | 'codex'} kind який LLM CLI запускати
|
|
181
187
|
* @param {string} prompt промпт для передачі у stdin
|
|
182
188
|
* @param {string} projectDir робочий каталог дочірнього процесу
|
|
183
189
|
* @param {(line: string) => void} logError вивід попередження/помилок
|
|
190
|
+
* @param {boolean} isolated чи скіл ізольований (worktree/requireRoot) — дозволяє force-approve для `cursor`
|
|
184
191
|
* @returns {number} exit code дочірнього процесу
|
|
185
192
|
*/
|
|
186
|
-
function runLlmCli(kind, prompt, projectDir, logError) {
|
|
193
|
+
function runLlmCli(kind, prompt, projectDir, logError, isolated) {
|
|
187
194
|
const runner = EXTERNAL_RUNNERS[kind]
|
|
188
195
|
|
|
189
196
|
if (runner.deprecated) {
|
|
@@ -194,7 +201,9 @@ function runLlmCli(kind, prompt, projectDir, logError) {
|
|
|
194
201
|
throw new Error(`\`${runner.bin}\` not found in PATH. Install ${kind} CLI or use \`skill pi\`.`)
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
const
|
|
204
|
+
const args = kind === 'cursor' && isolated ? [...runner.args, '--force'] : runner.args
|
|
205
|
+
|
|
206
|
+
const result = spawnSync(runner.bin, args, {
|
|
198
207
|
input: prompt,
|
|
199
208
|
cwd: projectDir,
|
|
200
209
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
@@ -252,7 +261,9 @@ export async function runSkillsCli(argv, options = {}) {
|
|
|
252
261
|
if (first === 'pi') {
|
|
253
262
|
return await runPiRunner(prompt, second, skillsRoot, projectDir, logError, deps)
|
|
254
263
|
}
|
|
255
|
-
|
|
264
|
+
const skillMeta = readSkillMetaRaw(join(skillsRoot, normalizeSkillId(second)))
|
|
265
|
+
const isolated = skillRequiresRoot(skillMeta)
|
|
266
|
+
return runLlmCli(/** @type {'claude' | 'cursor' | 'codex'} */ (first), prompt, projectDir, logError, isolated)
|
|
256
267
|
}
|
|
257
268
|
|
|
258
269
|
if (skillIds.includes(normalizeSkillId(first))) {
|