@nitra/cursor 1.9.21 → 1.10.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.
- package/.claude-template/hooks/capture-decisions.sh +3 -3
- package/.claude-template/hooks/normalize-decisions.sh +370 -0
- package/CHANGELOG.md +43 -0
- package/bin/n-cursor.js +56 -49
- package/package.json +10 -5
- package/rules/abie/auto.md +1 -0
- package/{scripts/check-abie.mjs → rules/abie/js/check.mjs} +5 -5
- package/rules/adr/adr.mdc +150 -0
- package/{scripts/check-adr.mjs → rules/adr/js/check.mjs} +85 -41
- package/rules/adr/policy/settings_json/settings_json.rego +37 -0
- package/rules/adr/policy/settings_local_json/settings_local_json.rego +40 -0
- package/rules/bun/auto.md +1 -0
- package/{scripts/check-bun.mjs → rules/bun/js/check.mjs} +1 -1
- package/rules/capacitor/auto.md +1 -0
- package/{scripts/check-capacitor.mjs → rules/capacitor/js/check.mjs} +1 -1
- package/rules/changelog/auto.md +1 -0
- package/{scripts/check-changelog.mjs → rules/changelog/js/check.mjs} +2 -2
- package/rules/docker/auto.md +1 -0
- package/{scripts/check-docker.mjs → rules/docker/js/check.mjs} +5 -5
- package/{scripts/run-docker.mjs → rules/docker/js/run.mjs} +5 -5
- package/rules/ga/auto.md +1 -0
- package/{scripts/check-ga.mjs → rules/ga/js/check.mjs} +4 -4
- package/{scripts/lint-ga.mjs → rules/ga/js/lint.mjs} +2 -2
- package/rules/graphql/auto.md +1 -0
- package/{scripts/check-graphql.mjs → rules/graphql/js/check.mjs} +5 -5
- package/rules/hasura/auto.md +1 -0
- package/{scripts/check-hasura.mjs → rules/hasura/js/check.mjs} +4 -4
- package/rules/image-avif/auto.md +1 -0
- package/{scripts/check-image-avif.mjs → rules/image-avif/js/check.mjs} +5 -5
- package/rules/image-compress/auto.md +1 -0
- package/{scripts/check-image-compress.mjs → rules/image-compress/js/check.mjs} +1 -1
- package/rules/js-bun-db/auto.md +1 -0
- package/{scripts/check-js-bun-db.mjs → rules/js-bun-db/js/check.mjs} +5 -5
- package/rules/js-bun-redis/auto.md +1 -0
- package/{scripts/check-js-bun-redis.mjs → rules/js-bun-redis/js/check.mjs} +4 -4
- package/rules/js-lint/auto.md +1 -0
- package/{scripts/check-js-lint.mjs → rules/js-lint/js/check.mjs} +1 -1
- package/rules/js-mssql/auto.md +1 -0
- package/{scripts/check-js-mssql.mjs → rules/js-mssql/js/check.mjs} +5 -5
- package/rules/js-run/auto.md +1 -0
- package/{scripts/check-js-run.mjs → rules/js-run/js/check.mjs} +11 -11
- package/rules/k8s/auto.md +1 -0
- package/{scripts/check-k8s.mjs → rules/k8s/js/check.mjs} +4 -4
- package/{scripts/run-k8s.mjs → rules/k8s/js/run.mjs} +4 -4
- package/rules/nginx-default-tpl/auto.md +1 -0
- package/{scripts/check-nginx-default-tpl.mjs → rules/nginx-default-tpl/js/check.mjs} +7 -7
- package/rules/npm-module/auto.md +1 -0
- package/{scripts/check-npm-module.mjs → rules/npm-module/js/check.mjs} +4 -4
- package/rules/php/auto.md +1 -0
- package/{scripts/check-php.mjs → rules/php/js/check.mjs} +1 -1
- package/{scripts/run-php.mjs → rules/php/js/run.mjs} +3 -3
- package/rules/rego/auto.md +1 -0
- package/{scripts/check-rego.mjs → rules/rego/js/check.mjs} +4 -4
- package/{scripts/lint-rego.mjs → rules/rego/js/lint.mjs} +1 -1
- package/rules/style-lint/auto.md +1 -0
- package/{scripts/check-style-lint.mjs → rules/style-lint/js/check.mjs} +1 -1
- package/rules/tauri/auto.md +1 -0
- package/{scripts/check-tauri.mjs → rules/tauri/js/check.mjs} +2 -2
- package/rules/text/auto.md +1 -0
- package/{scripts/check-text.mjs → rules/text/js/check.mjs} +2 -2
- package/{scripts/run-shellcheck-text.mjs → rules/text/js/run-shellcheck.mjs} +2 -2
- package/{scripts → rules/text/js}/run-v8r.mjs +2 -2
- package/{mdc → rules/text}/text.mdc +4 -0
- package/rules/vue/auto.md +1 -0
- package/{scripts/check-vue.mjs → rules/vue/js/check.mjs} +5 -5
- package/scripts/auto-rules.mjs +5 -5
- package/scripts/auto-skills.mjs +8 -6
- package/scripts/lint-conftest.mjs +13 -13
- package/scripts/sync-claude-config.mjs +70 -14
- package/scripts/utils/run-conftest-batch.mjs +9 -4
- package/skills/abie-clean/auto.md +1 -0
- package/skills/abie-kustomize/auto.md +1 -0
- package/skills/adr-normalize/SKILL.md +71 -0
- package/skills/adr-normalize/auto.md +1 -0
- package/skills/fix/auto.md +1 -0
- package/skills/lint/auto.md +1 -0
- package/skills/llm-patch/auto.md +1 -0
- package/skills/publish-telegram/auto.md +1 -0
- package/skills/taze/auto.md +1 -0
- package/bin/auto-rules.md +0 -59
- package/bin/auto-skills.md +0 -25
- package/mdc/adr.mdc +0 -86
- package/policy/adr/settings_json/settings_json.rego +0 -31
- package/policy/adr/settings_local_json/settings_local_json.rego +0 -28
- /package/{mdc → rules/abie}/abie.mdc +0 -0
- /package/{policy/abie → rules/abie/policy}/base_deployment_preem/base_deployment_preem.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/base_deployment_preem/base_deployment_preem_test.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/health_check_policy/health_check_policy.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/health_check_policy/health_check_policy_test.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/http_route_base/http_route_base.rego +0 -0
- /package/{policy/abie → rules/abie/policy}/http_route_base/http_route_base_test.rego +0 -0
- /package/{mdc → rules/bun}/bun.mdc +0 -0
- /package/{policy/bun → rules/bun/policy}/bunfig/bunfig.rego +0 -0
- /package/{policy/bun → rules/bun/policy}/package_json/package_json.rego +0 -0
- /package/{policy/bun → rules/bun/policy}/package_json/package_json_test.rego +0 -0
- /package/{mdc → rules/capacitor}/capacitor.mdc +0 -0
- /package/{policy/capacitor → rules/capacitor/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/changelog}/changelog.mdc +0 -0
- /package/{mdc → rules/ci4}/ci4.mdc +0 -0
- /package/{mdc → rules/docker}/docker.mdc +0 -0
- /package/{policy/docker → rules/docker/policy}/lint_docker_yml/lint_docker_yml.rego +0 -0
- /package/{policy/docker → rules/docker/policy}/lint_docker_yml/lint_docker_yml_test.rego +0 -0
- /package/{policy/docker → rules/docker/policy}/package_json/package_json.rego +0 -0
- /package/{policy/docker → rules/docker/policy}/package_json/package_json_test.rego +0 -0
- /package/{mdc → rules/ga}/ga.mdc +0 -0
- /package/{policy/ga → rules/ga/policy}/clean_ga_workflows/clean_ga_workflows.rego +0 -0
- /package/{policy/ga → rules/ga/policy}/clean_merged_branch/clean_merged_branch.rego +0 -0
- /package/{policy/ga → rules/ga/policy}/git_ai/git_ai.rego +0 -0
- /package/{policy/ga → rules/ga/policy}/lint_ga/lint_ga.rego +0 -0
- /package/{policy/ga → rules/ga/policy}/workflow_common/workflow_common.rego +0 -0
- /package/{mdc → rules/graphql}/graphql.mdc +0 -0
- /package/{policy/graphql → rules/graphql/policy}/vscode_extensions/vscode_extensions.rego +0 -0
- /package/{policy/graphql → rules/graphql/policy}/vscode_extensions/vscode_extensions_test.rego +0 -0
- /package/{mdc → rules/hasura}/hasura.mdc +0 -0
- /package/{policy/hasura → rules/hasura/policy}/svc_hl/svc_hl.rego +0 -0
- /package/{mdc → rules/image-avif}/image-avif.mdc +0 -0
- /package/{policy/image_avif → rules/image-avif/policy}/package_json/package_json.rego +0 -0
- /package/{policy/image_avif → rules/image-avif/policy}/package_json/package_json_test.rego +0 -0
- /package/{mdc → rules/image-compress}/image-compress.mdc +0 -0
- /package/{policy/image_compress → rules/image-compress/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/js-bun-db}/js-bun-db.mdc +0 -0
- /package/{policy/js_bun_db → rules/js-bun-db/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/js-bun-redis}/js-bun-redis.mdc +0 -0
- /package/{policy/js_bun_redis → rules/js-bun-redis/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/js-lint}/js-lint.mdc +0 -0
- /package/{policy/js_lint → rules/js-lint/policy}/lint_js_yml/lint_js_yml.rego +0 -0
- /package/{policy/js_lint → rules/js-lint/policy}/package_json/package_json.rego +0 -0
- /package/{policy/js_lint → rules/js-lint/policy}/package_json/package_json_test.rego +0 -0
- /package/{mdc → rules/js-mssql}/js-mssql.mdc +0 -0
- /package/{policy/js_mssql → rules/js-mssql/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/js-run}/js-run.mdc +0 -0
- /package/{policy/js_run → rules/js-run/policy}/configmap/configmap.rego +0 -0
- /package/{policy/js_run → rules/js-run/policy}/jsconfig/jsconfig.rego +0 -0
- /package/{policy/js_run → rules/js-run/policy}/jsconfig/jsconfig_test.rego +0 -0
- /package/{policy/js_run → rules/js-run/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/k8s}/k8s.mdc +0 -0
- /package/{policy/k8s → rules/k8s/policy}/base_kustomization/base_kustomization.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/base_kustomization/base_kustomization_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/base_manifest/base_manifest.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/base_manifest/base_manifest_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/gateway/gateway.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/gateway/gateway_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/hasura_configmap/hasura_configmap.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/hasura_configmap/hasura_configmap_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/hasura_httproute/hasura_httproute.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/hasura_httproute/hasura_httproute_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/hpa_pdb/hpa_pdb.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/hpa_pdb/hpa_pdb_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/kustomization/kustomization.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/kustomization/kustomization_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/manifest/manifest.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/manifest/manifest_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/svc_hl_yaml/svc_hl_yaml.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/svc_hl_yaml/svc_hl_yaml_test.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/svc_yaml/svc_yaml.rego +0 -0
- /package/{policy/k8s → rules/k8s/policy}/svc_yaml/svc_yaml_test.rego +0 -0
- /package/{mdc → rules/nginx-default-tpl}/nginx-default-tpl.mdc +0 -0
- /package/{policy/nginx_default_tpl → rules/nginx-default-tpl/policy}/vscode_extensions/vscode_extensions.rego +0 -0
- /package/{policy/nginx_default_tpl → rules/nginx-default-tpl/policy}/vscode_extensions/vscode_extensions_test.rego +0 -0
- /package/{policy/nginx_default_tpl → rules/nginx-default-tpl/policy}/vscode_settings/vscode_settings.rego +0 -0
- /package/{policy/nginx_default_tpl → rules/nginx-default-tpl/policy}/vscode_settings/vscode_settings_test.rego +0 -0
- /package/{mdc → rules/npm-module}/npm-module.mdc +0 -0
- /package/{policy/npm_module → rules/npm-module/policy}/emit_types_config/emit_types_config.rego +0 -0
- /package/{policy/npm_module → rules/npm-module/policy}/npm_package_json/npm_package_json.rego +0 -0
- /package/{policy/npm_module → rules/npm-module/policy}/npm_package_json/npm_package_json_test.rego +0 -0
- /package/{policy/npm_module → rules/npm-module/policy}/npm_publish_yml/npm_publish_yml.rego +0 -0
- /package/{policy/npm_module → rules/npm-module/policy}/root_package_json/root_package_json.rego +0 -0
- /package/{mdc → rules/php}/php.mdc +0 -0
- /package/{policy/php → rules/php/policy}/lint_php_yml/lint_php_yml.rego +0 -0
- /package/{policy/php → rules/php/policy}/package_json/package_json.rego +0 -0
- /package/{policy/rego → rules/rego/policy}/package_json/package_json.rego +0 -0
- /package/{policy/rego → rules/rego/policy}/package_json/package_json_test.rego +0 -0
- /package/{policy/rego → rules/rego/policy}/vscode_extensions/vscode_extensions.rego +0 -0
- /package/{policy/rego → rules/rego/policy}/vscode_extensions/vscode_extensions_test.rego +0 -0
- /package/{policy/rego → rules/rego/policy}/vscode_settings/vscode_settings.rego +0 -0
- /package/{policy/rego → rules/rego/policy}/vscode_settings/vscode_settings_test.rego +0 -0
- /package/{mdc → rules/rego}/rego.mdc +0 -0
- /package/{policy/style_lint → rules/style-lint/policy}/lint_style_yml/lint_style_yml.rego +0 -0
- /package/{policy/style_lint → rules/style-lint/policy}/package_json/package_json.rego +0 -0
- /package/{policy/style_lint → rules/style-lint/policy}/vscode_extensions/vscode_extensions.rego +0 -0
- /package/{policy/style_lint → rules/style-lint/policy}/vscode_extensions/vscode_extensions_test.rego +0 -0
- /package/{policy/style_lint → rules/style-lint/policy}/vscode_settings/vscode_settings.rego +0 -0
- /package/{policy/style_lint → rules/style-lint/policy}/vscode_settings/vscode_settings_test.rego +0 -0
- /package/{mdc → rules/style-lint}/style-lint.mdc +0 -0
- /package/{policy/tauri → rules/tauri/policy}/vscode_extensions/vscode_extensions.rego +0 -0
- /package/{policy/tauri → rules/tauri/policy}/vscode_extensions/vscode_extensions_test.rego +0 -0
- /package/{mdc → rules/tauri}/tauri.mdc +0 -0
- /package/{policy/text → rules/text/policy}/cspell/cspell.rego +0 -0
- /package/{policy/text → rules/text/policy}/markdownlint/markdownlint.rego +0 -0
- /package/{policy/text → rules/text/policy}/markdownlint/markdownlint_test.rego +0 -0
- /package/{policy/text → rules/text/policy}/oxfmtrc/oxfmtrc.rego +0 -0
- /package/{policy/text → rules/text/policy}/package_json/package_json.rego +0 -0
- /package/{policy/text → rules/text/policy}/vscode_extensions/vscode_extensions.rego +0 -0
- /package/{policy/text → rules/text/policy}/vscode_extensions/vscode_extensions_test.rego +0 -0
- /package/{policy/text → rules/text/policy}/vscode_settings/vscode_settings.rego +0 -0
- /package/{policy/text → rules/text/policy}/vscode_settings/vscode_settings_test.rego +0 -0
- /package/{policy/vue → rules/vue/policy}/package_json/package_json.rego +0 -0
- /package/{mdc → rules/vue}/vue.mdc +0 -0
package/bin/n-cursor.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* Якщо у корені репозиторію немає .n-cursor.json, спочатку перейменовується за наявності nitra-cursor.json;
|
|
23
23
|
* у `.cursor/rules` файли `nitra-*.mdc` перейменовуються на `n-*.mdc`; інакше конфіг створюється автоматично
|
|
24
|
-
* з усіма правилами з каталогу
|
|
24
|
+
* з усіма правилами з каталогу rules/ пакету (їх можна відредагувати після створення). У файлі завжди має бути
|
|
25
25
|
* поле `$schema` з посиланням на JSON Schema пакету (публічний URL для IDE); при зчитуванні конфігу воно додається або виправляється на диску, якщо відсутнє або некоректне.
|
|
26
26
|
* Масиви `rules`, `skills`, `disable-rules` і `disable-skills` при записі сортуються за алфавітом.
|
|
27
27
|
*
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* `github-actions/` пакету при кожному успішному синку (workflows з правил ga / js-lint / text).
|
|
37
37
|
*
|
|
38
38
|
* Skills копіюються з npm/skills пакету лише для id з масиву «skills» у .n-cursor.json
|
|
39
|
-
* (у JSON — без префікса, як імена
|
|
39
|
+
* (у JSON — без префікса, як імена каталогів у rules/ без n-). У пакеті джерело — каталоги
|
|
40
40
|
* skills/<id>/ (без префікса); у проєкті — .cursor/skills/n-<id>/ (префікс n-, як n-*.mdc).
|
|
41
41
|
* Якщо ключа skills немає, за замовчуванням підтягуються всі підкаталоги skills/ (лише імена без префікса n-).
|
|
42
42
|
* Зайві каталоги n-* у .cursor/skills, яких немає у списку, видаляються.
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
import { detectAutoSkills } from '../scripts/auto-skills.mjs'
|
|
68
68
|
import { runStopHookCli } from '../scripts/claude-stop-hook.mjs'
|
|
69
69
|
import { ensureNitraCursorInRootDevDependencies } from '../scripts/ensure-nitra-cursor-dev-dependencies.mjs'
|
|
70
|
-
import { runLintGaCli } from '../
|
|
70
|
+
import { runLintGaCli } from '../rules/ga/js/lint.mjs'
|
|
71
71
|
import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
|
|
72
72
|
import { upgradeNitraCursorToLatestAndBunInstall } from '../scripts/upgrade-nitra-cursor-and-install.mjs'
|
|
73
73
|
import { runRenameYamlExtensionsCli } from './rename-yaml-extensions.mjs'
|
|
@@ -85,11 +85,11 @@ const COMMANDS_DIR = '.claude/commands'
|
|
|
85
85
|
const RULE_PREFIX = 'n-'
|
|
86
86
|
|
|
87
87
|
const binDir = dirname(fileURLToPath(import.meta.url))
|
|
88
|
-
const
|
|
88
|
+
const BUNDLED_RULES_DIR = join(binDir, '..', 'rules')
|
|
89
89
|
const BUNDLED_SCRIPTS_DIR = join(binDir, '..', 'scripts')
|
|
90
90
|
const BUNDLED_SKILLS_DIR = join(binDir, '..', 'skills')
|
|
91
91
|
const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
|
|
92
|
-
/** Корінь установленого пакету (каталог з `
|
|
92
|
+
/** Корінь установленого пакету (каталог з `rules/`, `github-actions/`, …) */
|
|
93
93
|
const BUNDLED_PACKAGE_ROOT = join(binDir, '..')
|
|
94
94
|
|
|
95
95
|
const YAML_FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/
|
|
@@ -116,25 +116,27 @@ function sortConfigIdArrays(config) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Імена правил
|
|
120
|
-
*
|
|
121
|
-
* @
|
|
119
|
+
* Імена правил з каталогу `rules/` поточної інсталяції пакету. Кожне правило — окремий
|
|
120
|
+
* підкаталог `rules/<id>/`, у якому має бути `<id>.mdc`.
|
|
121
|
+
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету
|
|
122
|
+
* @returns {Promise<string[]>} відсортовані id правил (імена підкаталогів)
|
|
122
123
|
*/
|
|
123
|
-
async function discoverBundledRuleNames(
|
|
124
|
-
if (!existsSync(
|
|
124
|
+
async function discoverBundledRuleNames(bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
125
|
+
if (!existsSync(bundledRulesDir)) {
|
|
125
126
|
throw new Error(
|
|
126
127
|
`Не знайдено каталог правил пакету.\n` +
|
|
127
|
-
`Очікуваний шлях: ${
|
|
128
|
+
`Очікуваний шлях: ${bundledRulesDir}\n` +
|
|
128
129
|
`Перевстановіть ${PACKAGE_NAME} або створіть ${CONFIG_FILE} вручну.`
|
|
129
130
|
)
|
|
130
131
|
}
|
|
131
|
-
const
|
|
132
|
-
const rules =
|
|
133
|
-
.filter(
|
|
134
|
-
.
|
|
132
|
+
const entries = await readdir(bundledRulesDir, { withFileTypes: true })
|
|
133
|
+
const rules = entries
|
|
134
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
135
|
+
.filter(e => existsSync(join(bundledRulesDir, e.name, `${e.name}.mdc`)))
|
|
136
|
+
.map(e => e.name)
|
|
135
137
|
.toSorted((a, b) => a.localeCompare(b))
|
|
136
138
|
if (rules.length === 0) {
|
|
137
|
-
throw new Error(`У каталозі
|
|
139
|
+
throw new Error(`У каталозі rules/ пакету немає підкаталогів з <id>.mdc. Створіть ${CONFIG_FILE} вручну.`)
|
|
138
140
|
}
|
|
139
141
|
return rules
|
|
140
142
|
}
|
|
@@ -203,15 +205,15 @@ async function migrateLegacyConfigIfNeeded() {
|
|
|
203
205
|
|
|
204
206
|
/**
|
|
205
207
|
* Зчитує конфіг .n-cursor.json з поточної директорії
|
|
206
|
-
* @param {{
|
|
208
|
+
* @param {{ bundledRulesDir?: string, bundledSkillsDir?: string }} [paths] каталоги з пакету-джерела (після `bun i` — зазвичай `node_modules/@nitra/cursor`)
|
|
207
209
|
* @returns {Promise<{ $schema: string, rules: string[], skills: string[], version?: string } & Record<string, unknown>>} rules, skills (id без префікса n-); поле version у файлі за наявності ігнорується при синхронізації правил
|
|
208
210
|
*/
|
|
209
211
|
async function readConfig(paths = {}) {
|
|
210
|
-
const
|
|
212
|
+
const bundledRulesDir = paths.bundledRulesDir ?? BUNDLED_RULES_DIR
|
|
211
213
|
const bundledSkillsDir = paths.bundledSkillsDir ?? BUNDLED_SKILLS_DIR
|
|
212
214
|
await migrateLegacyConfigIfNeeded()
|
|
213
215
|
const configPath = join(cwd(), CONFIG_FILE)
|
|
214
|
-
const availableRules = await discoverBundledRuleNames(
|
|
216
|
+
const availableRules = await discoverBundledRuleNames(bundledRulesDir)
|
|
215
217
|
const availableSkills = await discoverBundledSkillNames(bundledSkillsDir)
|
|
216
218
|
|
|
217
219
|
/**
|
|
@@ -231,7 +233,7 @@ async function readConfig(paths = {}) {
|
|
|
231
233
|
}
|
|
232
234
|
|
|
233
235
|
/**
|
|
234
|
-
* Автодописує правила/skills за `auto
|
|
236
|
+
* Автодописує правила/skills за `rules/<rule>/auto.md` і синхронізує `$schema`.
|
|
235
237
|
* @param {Record<string, unknown>} parsedConfig сирий обʼєкт конфігу
|
|
236
238
|
* @returns {Promise<Record<string, unknown>>} нормалізований конфіг
|
|
237
239
|
*/
|
|
@@ -350,29 +352,31 @@ function logRuleMigrationsIfAny(parsedConfig) {
|
|
|
350
352
|
}
|
|
351
353
|
|
|
352
354
|
/**
|
|
353
|
-
* Витягує
|
|
354
|
-
* "npm/
|
|
355
|
-
* "text"
|
|
355
|
+
* Витягує чистий id правила без шляху і без .mdc.
|
|
356
|
+
* "npm/rules/text/text.mdc" → "text"
|
|
357
|
+
* "text.mdc" → "text"
|
|
358
|
+
* "text" → "text"
|
|
356
359
|
* @param {string} ruleName шлях або базове ім'я, з суфіксом .mdc або без
|
|
357
|
-
* @returns {string}
|
|
360
|
+
* @returns {string} id правила (без .mdc, без шляху)
|
|
358
361
|
*/
|
|
359
362
|
function normalizeRuleName(ruleName) {
|
|
360
|
-
const name = ruleName.
|
|
361
|
-
return
|
|
363
|
+
const name = basename(String(ruleName).trim())
|
|
364
|
+
return name.endsWith('.mdc') ? name.slice(0, -'.mdc'.length) : name
|
|
362
365
|
}
|
|
363
366
|
|
|
364
367
|
/**
|
|
365
|
-
* Читає вміст правила з каталогу `mdc
|
|
368
|
+
* Читає вміст правила з каталогу `rules/<id>/<id>.mdc` установленого пакету
|
|
369
|
+
* (наприклад `node_modules/@nitra/cursor/rules/<id>/<id>.mdc` або кеш npx).
|
|
366
370
|
* @param {string} rule елемент масиву rules з `.n-cursor.json`
|
|
367
|
-
* @param {string} [
|
|
371
|
+
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
|
|
368
372
|
* @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
|
|
369
373
|
*/
|
|
370
|
-
function readBundledRuleContent(rule,
|
|
371
|
-
const
|
|
372
|
-
const bundledPath = join(
|
|
374
|
+
function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
375
|
+
const id = normalizeRuleName(rule)
|
|
376
|
+
const bundledPath = join(bundledRulesDir, id, `${id}.mdc`)
|
|
373
377
|
if (!existsSync(bundledPath)) {
|
|
374
378
|
throw new Error(
|
|
375
|
-
`Немає файлу ${
|
|
379
|
+
`Немає файлу ${id}/${id}.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
|
|
376
380
|
)
|
|
377
381
|
}
|
|
378
382
|
return readFile(bundledPath, 'utf8')
|
|
@@ -515,7 +519,7 @@ async function listProjectRulesMdcFiles() {
|
|
|
515
519
|
* @returns {Set<string>} множина очікуваних імен файлів (наприклад n-bun.mdc)
|
|
516
520
|
*/
|
|
517
521
|
function expectedManagedRuleBasenames(configRules) {
|
|
518
|
-
return new Set(configRules.map(rule => `${RULE_PREFIX}${normalizeRuleName(rule)}`))
|
|
522
|
+
return new Set(configRules.map(rule => `${RULE_PREFIX}${normalizeRuleName(rule)}.mdc`))
|
|
519
523
|
}
|
|
520
524
|
|
|
521
525
|
/**
|
|
@@ -939,19 +943,19 @@ async function runSyncStep(prefix, action) {
|
|
|
939
943
|
/**
|
|
940
944
|
* Копіює керовані `.mdc` файли з пакету до `.cursor/rules`.
|
|
941
945
|
* @param {string[]} rules список rules з конфігу
|
|
942
|
-
* @param {string}
|
|
946
|
+
* @param {string} bundledRulesDir каталог `mdc` пакету-джерела
|
|
943
947
|
* @param {string} rulesDir абсолютний шлях до `.cursor/rules`
|
|
944
948
|
* @returns {Promise<{ successCount: number, failCount: number }>} статистика копіювання
|
|
945
949
|
*/
|
|
946
|
-
async function syncManagedRuleFiles(rules,
|
|
950
|
+
async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir) {
|
|
947
951
|
let successCount = 0
|
|
948
952
|
let failCount = 0
|
|
949
953
|
for (const rule of rules) {
|
|
950
|
-
const fileName = `${RULE_PREFIX}${normalizeRuleName(rule)}`
|
|
954
|
+
const fileName = `${RULE_PREFIX}${normalizeRuleName(rule)}.mdc`
|
|
951
955
|
const destPath = join(rulesDir, fileName)
|
|
952
956
|
try {
|
|
953
957
|
process.stdout.write(` ⬇ ${rule} → ${RULES_DIR}/${fileName} ... `)
|
|
954
|
-
const content = await readBundledRuleContent(rule,
|
|
958
|
+
const content = await readBundledRuleContent(rule, bundledRulesDir)
|
|
955
959
|
await writeFile(destPath, content, 'utf8')
|
|
956
960
|
console.log(`✅`)
|
|
957
961
|
successCount++
|
|
@@ -982,15 +986,17 @@ function logRemovedManagedItems(title, basePath, names) {
|
|
|
982
986
|
}
|
|
983
987
|
|
|
984
988
|
/**
|
|
985
|
-
* Знаходить доступні check
|
|
986
|
-
*
|
|
989
|
+
* Знаходить доступні check-скрипти. Кожне правило з check-скриптом тримає його у
|
|
990
|
+
* `rules/<id>/js/check.mjs` (rule-centric layout).
|
|
991
|
+
* @returns {Promise<string[]>} відсортовані id правил, для яких є check.mjs
|
|
987
992
|
*/
|
|
988
993
|
async function discoverCheckScripts() {
|
|
989
|
-
if (!existsSync(
|
|
990
|
-
const
|
|
991
|
-
return
|
|
992
|
-
.filter(
|
|
993
|
-
.
|
|
994
|
+
if (!existsSync(BUNDLED_RULES_DIR)) return []
|
|
995
|
+
const entries = await readdir(BUNDLED_RULES_DIR, { withFileTypes: true })
|
|
996
|
+
return entries
|
|
997
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
998
|
+
.filter(e => existsSync(join(BUNDLED_RULES_DIR, e.name, 'js', 'check.mjs')))
|
|
999
|
+
.map(e => e.name)
|
|
994
1000
|
.toSorted((a, b) => a.localeCompare(b))
|
|
995
1001
|
}
|
|
996
1002
|
|
|
@@ -1090,10 +1096,10 @@ async function runChecks(requestedRules) {
|
|
|
1090
1096
|
let totalFailed = 0
|
|
1091
1097
|
|
|
1092
1098
|
for (const rule of rulesToCheck) {
|
|
1093
|
-
const scriptPath = join(
|
|
1099
|
+
const scriptPath = join(BUNDLED_RULES_DIR, rule, 'js', 'check.mjs')
|
|
1094
1100
|
console.log(`📋 ${rule}:`)
|
|
1095
1101
|
try {
|
|
1096
|
-
// eslint-disable-next-line no-unsanitized/method -- rule валідовано проти available, scriptPath будується з фіксованої
|
|
1102
|
+
// eslint-disable-next-line no-unsanitized/method -- rule валідовано проти available, scriptPath будується з фіксованої BUNDLED_RULES_DIR
|
|
1097
1103
|
const { check } = await import(scriptPath)
|
|
1098
1104
|
const code = await check()
|
|
1099
1105
|
if (code !== 0) totalFailed++
|
|
@@ -1201,11 +1207,11 @@ async function runSync() {
|
|
|
1201
1207
|
|
|
1202
1208
|
await reexecIfPackageVersionChanged(effectivePackageRoot)
|
|
1203
1209
|
|
|
1204
|
-
const
|
|
1210
|
+
const bundledRulesDir = join(effectivePackageRoot, 'mdc')
|
|
1205
1211
|
const bundledSkillsDir = join(effectivePackageRoot, 'skills')
|
|
1206
1212
|
const bundledAgentsTemplatePath = join(effectivePackageRoot, AGENTS_TEMPLATE_FILE)
|
|
1207
1213
|
|
|
1208
|
-
const config = await runSyncStep('❌ ', () => readConfig({
|
|
1214
|
+
const config = await runSyncStep('❌ ', () => readConfig({ bundledRulesDir, bundledSkillsDir }))
|
|
1209
1215
|
|
|
1210
1216
|
const { rules, skills, version, ignore } = config
|
|
1211
1217
|
const claudeConfigEnabled = config['claude-config'] !== false
|
|
@@ -1230,7 +1236,7 @@ async function runSync() {
|
|
|
1230
1236
|
|
|
1231
1237
|
const rulesDir = join(cwd(), RULES_DIR)
|
|
1232
1238
|
await mkdir(rulesDir, { recursive: true })
|
|
1233
|
-
const { successCount, failCount } = await syncManagedRuleFiles(rules,
|
|
1239
|
+
const { successCount, failCount } = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir)
|
|
1234
1240
|
|
|
1235
1241
|
await runSyncStep(`❌ Не вдалося прибрати зайві файли в ${RULES_DIR}: `, async () => {
|
|
1236
1242
|
const removed = await removeOrphanManagedRuleFiles(rulesDir, rules)
|
|
@@ -1288,6 +1294,7 @@ async function runSync() {
|
|
|
1288
1294
|
if (result.npmClaudeMd) parts.push('npm/CLAUDE.md')
|
|
1289
1295
|
if (result.commands.length > 0) parts.push(`${result.commands.length} slash-commands`)
|
|
1290
1296
|
if (result.adrHook) parts.push('.claude/hooks/capture-decisions.sh')
|
|
1297
|
+
if (result.adrNormalizeHook) parts.push('.claude/hooks/normalize-decisions.sh')
|
|
1291
1298
|
if (parts.length > 0) {
|
|
1292
1299
|
console.log(`🤖 Claude-конфіг: ${parts.join(', ')}`)
|
|
1293
1300
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -24,21 +24,23 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"types",
|
|
27
|
-
"
|
|
27
|
+
"rules",
|
|
28
28
|
"bin",
|
|
29
29
|
"github-actions",
|
|
30
|
-
"policy",
|
|
31
30
|
"schemas",
|
|
32
31
|
"scripts",
|
|
33
32
|
"skills",
|
|
34
33
|
".claude-template",
|
|
35
34
|
"AGENTS.template.md",
|
|
36
|
-
"CHANGELOG.md"
|
|
35
|
+
"CHANGELOG.md",
|
|
36
|
+
"!**/*.test.mjs",
|
|
37
|
+
"!**/test-helpers.mjs",
|
|
38
|
+
"!**/fixtures/**"
|
|
37
39
|
],
|
|
38
40
|
"type": "module",
|
|
39
41
|
"types": "./types/bin/n-cursor.d.ts",
|
|
40
42
|
"scripts": {
|
|
41
|
-
"test": "bun test
|
|
43
|
+
"test": "bun test",
|
|
42
44
|
"rename-yaml-extensions": "bun ./bin/n-cursor.js rename-yaml-extensions"
|
|
43
45
|
},
|
|
44
46
|
"dependencies": {
|
|
@@ -48,5 +50,8 @@
|
|
|
48
50
|
"engines": {
|
|
49
51
|
"bun": ">=1.3",
|
|
50
52
|
"node": ">=25"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@nitra/cursor": "^1.9.22"
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
якщо в кореневому package.json в секції "repository" присутній текст "<https://github.com/abinbevefes/**/>"
|
|
@@ -45,11 +45,11 @@ import { basename, dirname, join, relative } from 'node:path'
|
|
|
45
45
|
|
|
46
46
|
import { parseAllDocuments } from 'yaml'
|
|
47
47
|
|
|
48
|
-
import { pathHasK8sSegment } from '
|
|
49
|
-
import { createCheckReporter } from '
|
|
50
|
-
import { loadCursorIgnorePaths } from '
|
|
51
|
-
import { runConftestBatch } from '
|
|
52
|
-
import { walkDir } from '
|
|
48
|
+
import { pathHasK8sSegment } from '../../k8s/js/check.mjs'
|
|
49
|
+
import { createCheckReporter } from '../../../scripts/utils/check-reporter.mjs'
|
|
50
|
+
import { loadCursorIgnorePaths } from '../../../scripts/utils/load-cursor-config.mjs'
|
|
51
|
+
import { runConftestBatch } from '../../../scripts/utils/run-conftest-batch.mjs'
|
|
52
|
+
import { walkDir } from '../../../scripts/utils/walkDir.mjs'
|
|
53
53
|
|
|
54
54
|
const CONFIG_FILE = '.n-cursor.json'
|
|
55
55
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Автоматичний збір ADR/Runbook/Knowledge-чернеток і батч-нормалізація у `docs/adr/` через Stop-хук Claude Code
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
version: '2.0'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Правило вмикається **вручну** — додай `"adr"` у масив `rules` файлу `.n-cursor.json`. У `auto-rules.md` його немає, бо корисність залежить від робочого процесу команди (не кожен проєкт хоче літати ADR-чернеткам у `docs/`).
|
|
8
|
+
|
|
9
|
+
Коли правило увімкнене, **`npx @nitra/cursor`** автоматично:
|
|
10
|
+
|
|
11
|
+
- копіює канонічний bash-скрипт у **`.claude/hooks/capture-decisions.sh`** (executable, повністю керується пакетом — на кожен sync перезаписується);
|
|
12
|
+
- копіює канонічний bash-скрипт у **`.claude/hooks/normalize-decisions.sh`** (батч-нормалізація чернеток через LLM);
|
|
13
|
+
- додає у **`hooks.Stop`** в **`.claude/settings.json`** дві managed-групи: capture (`async: true`, `timeout: 180`) і normalize (`async: true`, `timeout: 600`);
|
|
14
|
+
- ці зміни — додаткові до lint Stop-hook (`@nitra/cursor stop-hook`); усі три групи живуть поряд у `Stop`.
|
|
15
|
+
|
|
16
|
+
## Дві фази, один каталог
|
|
17
|
+
|
|
18
|
+
ADR живуть у єдиному каталозі **`docs/adr/`**. Є два стани файлу, які відрізняються YAML frontmatter:
|
|
19
|
+
|
|
20
|
+
- **Draft** — файл з frontmatter `session: …`, `captured: …`, `transcript: …` та timestamp-іменем `YYYYMMDD-HHMMSS-<sid>.md`. Пише `capture-decisions.sh` після кожної сесії.
|
|
21
|
+
- **Clean** — файл без frontmatter, з kebab-case-іменем `<slug>.md` (наприклад `ланцюжок-запуску-abie.md`). Створює `normalize-decisions.sh` або людина руками.
|
|
22
|
+
|
|
23
|
+
`normalize-decisions.sh` ніколи не чіпає clean-файли — крім випадку `merge-into`, коли дописує `## Update YYYY-MM-DD` в кінець наявного clean-файлу.
|
|
24
|
+
|
|
25
|
+
### Фаза 1 — Capture
|
|
26
|
+
|
|
27
|
+
Stop-hook `capture-decisions.sh` зчитує JSONL-транскрипт сесії (через `jq`), витягає текст, `thinking`-блоки та назви `tool_use`-викликів, передає компактний дайджест у LLM CLI з промптом українською і записує результат у **`docs/adr/<timestamp>-<session>.md`**, якщо модель повернула блок з шапкою `## ADR|Runbook|Knowledge …`. Якщо модель повернула `NONE` (тривіальна сесія) — нічого не пишеться. Рекурсію з внутрішнього виклику моделі блокує env-var `CAPTURE_DECISIONS_RUNNING=1`.
|
|
28
|
+
|
|
29
|
+
### Фаза 2 — Normalize
|
|
30
|
+
|
|
31
|
+
Stop-hook `normalize-decisions.sh` спрацьовує на тому самому `Stop`-евенті, але:
|
|
32
|
+
|
|
33
|
+
- Виходить миттєво, якщо чернеток (`session:` у frontmatter) менше ніж **`ADR_NORMALIZE_THRESHOLD`** (default 30).
|
|
34
|
+
- Виходить миттєво, якщо від попередньої спроби пройшло менше **`ADR_NORMALIZE_MIN_INTERVAL_HOURS`** годин (default 6) — щоб не крутитися щоразу, коли поріг постійний.
|
|
35
|
+
- Бере не більше **`ADR_NORMALIZE_BATCH`** чернеток (default 30, найстарші за іменем-timestamp), формує один промпт LLM і чекає JSON-відповідь зі списком операцій.
|
|
36
|
+
- Виходить миттєво, якщо репозиторій у стані `MERGE_HEAD` / `rebase-*` — небезпечно правити файли посеред конфлікту.
|
|
37
|
+
- Виходить миттєво, якщо інший normalize-запуск тримає `flock` на `.claude/hooks/.normalize.lock` (тільки де `flock` доступний).
|
|
38
|
+
|
|
39
|
+
LLM повертає масив операцій:
|
|
40
|
+
|
|
41
|
+
| `op` | Семантика | Поля |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `delete` | Чернетка тривіальна / повністю покрита іншим clean-ADR-ом. | `file`, `reason` |
|
|
44
|
+
| `rewrite` | Чернетка стає окремим clean-файлом: frontmatter знімається, ім'я → `<slug>.md`, додаються `**Status: Accepted**` і `**Date:**` з `captured`. | `file`, `slug`, `content` |
|
|
45
|
+
| `merge-into` | Чернетка повторює тему вже існуючого clean-файлу; дописуємо `## Update YYYY-MM-DD` у кінець `target`. | `file`, `target`, `additions` |
|
|
46
|
+
|
|
47
|
+
`slug` — kebab-case українською (`ланцюжок-запуску-abie`, `npm-publish-flow`); англійські технічні терміни лишаються англійською без транслітерації. Колізія slug-ів обробляється детермінованим суфіксом `-2`, `-3`.
|
|
48
|
+
|
|
49
|
+
### Жодних git-операцій
|
|
50
|
+
|
|
51
|
+
`normalize-decisions.sh` **не комітить, не `git add`, нічого з git**. Усі зміни — у робочому дереві. Розробник у зручний момент дивиться `git status` / `git diff` і вирішує: `git add` + commit, `git checkout -- <file>` для відкату, або правки руками. Це і є review-вікно.
|
|
52
|
+
|
|
53
|
+
### Recursion guard і ENV-керування
|
|
54
|
+
|
|
55
|
+
Інший LLM CLI, який запустить normalize, успадковує `ADR_NORMALIZE_RUNNING=1` — внутрішній Stop-hook вийде відразу. Доступні ENV:
|
|
56
|
+
|
|
57
|
+
| Змінна | Default | Призначення |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `ADR_NORMALIZE_THRESHOLD` | `30` | Поріг чернеток для запуску фази. |
|
|
60
|
+
| `ADR_NORMALIZE_BATCH` | `30` | Максимум чернеток у одному виклику LLM. |
|
|
61
|
+
| `ADR_NORMALIZE_MIN_INTERVAL_HOURS` | `6` | Мінімум між спробами (навіть якщо поріг). |
|
|
62
|
+
| `ADR_NORMALIZE_DRY` | `0` | `1` — лише лог запланованих операцій, без змін на диску. |
|
|
63
|
+
| `ADR_NORMALIZE_MODEL` | `sonnet` | Модель для `claude -p`. |
|
|
64
|
+
| `ADR_NORMALIZE_CURSOR_MODEL` | `claude-4.6-sonnet-medium` | Модель для `cursor-agent -p`. |
|
|
65
|
+
|
|
66
|
+
Для ручного запуску (поза порогом і поза Stop-хуком) є **`/n-adr-normalize`** — slash-команда тимчасово виставляє `ADR_NORMALIZE_THRESHOLD=0` і `ADR_NORMALIZE_MIN_INTERVAL_HOURS=0` та викликає скрипт напряму.
|
|
67
|
+
|
|
68
|
+
## LLM CLI: claude → cursor-agent fallback
|
|
69
|
+
|
|
70
|
+
Обидва скрипти обирають доступний CLI (порядок фіксований):
|
|
71
|
+
|
|
72
|
+
1. **`claude`** (Anthropic Claude Code CLI) — `claude -p --model "<model>"`.
|
|
73
|
+
2. **`cursor-agent`** (Cursor IDE CLI) — `cursor-agent -p --mode ask --output-format text --model "<model>"`.
|
|
74
|
+
3. Жодного CLI у `PATH` — скрипт виходить з кодом `0` і нічого не пише.
|
|
75
|
+
|
|
76
|
+
`--mode ask` для cursor-agent навмисно: режим Q&A read-only, без shell/edit-інструментів — для класифікації / нормалізації інструменти не потрібні.
|
|
77
|
+
|
|
78
|
+
## Структура каталогу
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
docs/adr/
|
|
82
|
+
├── YYYYMMDD-HHMMSS-<sid>.md # drafts (frontmatter session:/captured:/transcript:)
|
|
83
|
+
└── <slug>.md # clean ADR-и (без frontmatter)
|
|
84
|
+
.claude/hooks/
|
|
85
|
+
├── capture-decisions.sh # auto-synced з пакета
|
|
86
|
+
├── normalize-decisions.sh # auto-synced з пакета
|
|
87
|
+
├── capture-decisions.log # лог запусків capture (НЕ коміти)
|
|
88
|
+
├── normalize-decisions.log # лог запусків normalize (НЕ коміти)
|
|
89
|
+
├── .normalize-state # timestamp останнього normalize-запуску (НЕ коміти)
|
|
90
|
+
└── .normalize.lock # lock-файл (НЕ коміти)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`.gitignore` повинен містити рядки, що покривають **`.claude/hooks/*.log`** і службові файли normalize (`.claude/hooks/.normalize-*`).
|
|
94
|
+
|
|
95
|
+
> Якщо в репозиторії лишився старий каталог **`docs/adr/_inbox/`** з попередньої версії правила — `normalize-decisions.sh` бачить його рекурсивно й поступово розчистить. Можна також одразу `git mv docs/adr/_inbox/*.md docs/adr/` і прибрати порожній каталог.
|
|
96
|
+
|
|
97
|
+
## Stop-hook у `.claude/settings.json`
|
|
98
|
+
|
|
99
|
+
Канонічний запис, який вставляє sync (поряд із lint stop-hook):
|
|
100
|
+
|
|
101
|
+
```json title=".claude/settings.json"
|
|
102
|
+
{
|
|
103
|
+
"hooks": {
|
|
104
|
+
"Stop": [
|
|
105
|
+
{
|
|
106
|
+
"matcher": "",
|
|
107
|
+
"hooks": [
|
|
108
|
+
{
|
|
109
|
+
"type": "command",
|
|
110
|
+
"command": "npx --no @nitra/cursor stop-hook",
|
|
111
|
+
"timeout": 60
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"matcher": "",
|
|
117
|
+
"hooks": [
|
|
118
|
+
{
|
|
119
|
+
"type": "command",
|
|
120
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/capture-decisions.sh\"",
|
|
121
|
+
"async": true,
|
|
122
|
+
"timeout": 180
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"matcher": "",
|
|
128
|
+
"hooks": [
|
|
129
|
+
{
|
|
130
|
+
"type": "command",
|
|
131
|
+
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/normalize-decisions.sh\"",
|
|
132
|
+
"async": true,
|
|
133
|
+
"timeout": 600
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Усі три групи ідентифікуються пакетом за маркером у `command` (`@nitra/cursor stop-hook`, `.claude/hooks/capture-decisions.sh`, `.claude/hooks/normalize-decisions.sh`) — користувацькі hook-групи поряд не чіпаються. Якщо `adr` прибрати з `rules`, обидві ADR-групи автоматично видаляються на наступному `npx @nitra/cursor`.
|
|
143
|
+
|
|
144
|
+
## Локальні vs project-shared налаштування
|
|
145
|
+
|
|
146
|
+
Обидва Stop-hook'и ADR живуть у **project-shared** `.claude/settings.json` (закомічений), щоб механізм працював у всіх членів команди. Якщо хук колись був у `.claude/settings.local.json` — прибери дубль вручну: project-shared і local-копія створили б два запуски на одну подію.
|
|
147
|
+
|
|
148
|
+
## Перевірка
|
|
149
|
+
|
|
150
|
+
`npx @nitra/cursor check adr`
|