@7n/rules 1.0.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 +355 -0
- package/.claude-template/hooks/lib/tooling-only.sh +77 -0
- package/.claude-template/hooks/normalize-decisions.sh +574 -0
- package/.claude-template/settings.template.json +26 -0
- package/.pi-template/extensions/n-rules-adr/docs/index.md +28 -0
- package/.pi-template/extensions/n-rules-adr/index.ts +105 -0
- package/.pi-template/extensions/n-rules-adr/tsconfig.json +17 -0
- package/AGENTS.template.md +53 -0
- package/CHANGELOG.md +4277 -0
- package/README.md +189 -0
- package/bin/docs/n-rules.md +612 -0
- package/bin/docs/rename-yaml-extensions.md +207 -0
- package/bin/n-rules.js +1709 -0
- package/bin/rename-yaml-extensions.mjs +42 -0
- package/docs/index.md +12 -0
- package/docs/stryker.config.md +31 -0
- package/docs/vitest.config.md +36 -0
- package/github-actions/release/action.yml +9 -0
- package/github-actions/setup-bun-deps/action.yml +27 -0
- package/package.json +87 -0
- package/rules/abie/base_deployment_preem/base_deployment_preem.mdc +22 -0
- package/rules/abie/base_deployment_preem/base_deployment_preem.rego +54 -0
- package/rules/abie/base_deployment_preem/concern.json +8 -0
- package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.mdc +19 -0
- package/rules/abie/clean_merged_ignore_branches/clean_merged_ignore_branches.rego +81 -0
- package/rules/abie/clean_merged_ignore_branches/concern.json +8 -0
- package/rules/abie/clean_merged_ignore_branches/template/clean-merged-branch.yml.snippet.yml +9 -0
- package/rules/abie/docs/index.md +11 -0
- package/rules/abie/env_dns/concern.json +4 -0
- package/rules/abie/env_dns/docs/main.md +32 -0
- package/rules/abie/env_dns/env_dns.mdc +33 -0
- package/rules/abie/env_dns/main.mjs +47 -0
- package/rules/abie/firebase_hosting/concern.json +4 -0
- package/rules/abie/firebase_hosting/docs/main.md +41 -0
- package/rules/abie/firebase_hosting/firebase_hosting.mdc +3 -0
- package/rules/abie/firebase_hosting/main.mjs +46 -0
- package/rules/abie/hc_pairing/concern.json +4 -0
- package/rules/abie/hc_pairing/docs/main.md +34 -0
- package/rules/abie/hc_pairing/hc_pairing.mdc +23 -0
- package/rules/abie/hc_pairing/main.mjs +52 -0
- package/rules/abie/health_check_policy/concern.json +8 -0
- package/rules/abie/health_check_policy/health_check_policy.mdc +19 -0
- package/rules/abie/health_check_policy/health_check_policy.rego +172 -0
- package/rules/abie/http_route_base/concern.json +8 -0
- package/rules/abie/http_route_base/http_route_base.mdc +9 -0
- package/rules/abie/http_route_base/http_route_base.rego +45 -0
- package/rules/abie/lib/docs/enabled.md +38 -0
- package/rules/abie/lib/docs/env-dns.md +31 -0
- package/rules/abie/lib/docs/hc-yaml.md +33 -0
- package/rules/abie/lib/docs/http-route.md +31 -0
- package/rules/abie/lib/docs/index.md +18 -0
- package/rules/abie/lib/docs/k8s-tree.md +44 -0
- package/rules/abie/lib/docs/kustomization-patches.md +37 -0
- package/rules/abie/lib/docs/overlay-paths.md +42 -0
- package/rules/abie/lib/docs/yaml.md +33 -0
- package/rules/abie/lib/enabled.mjs +37 -0
- package/rules/abie/lib/env-dns.mjs +81 -0
- package/rules/abie/lib/hc-yaml.mjs +27 -0
- package/rules/abie/lib/http-route.mjs +100 -0
- package/rules/abie/lib/k8s-tree.mjs +110 -0
- package/rules/abie/lib/kustomization-patches.mjs +211 -0
- package/rules/abie/lib/overlay-paths.mjs +97 -0
- package/rules/abie/lib/yaml.mjs +72 -0
- package/rules/abie/main.json +1 -0
- package/rules/abie/main.mdc +47 -0
- package/rules/abie/package_json_shared/concern.json +11 -0
- package/rules/abie/package_json_shared/package_json_shared.mdc +17 -0
- package/rules/abie/package_json_shared/package_json_shared.rego +16 -0
- package/rules/abie/ua_http_route/concern.json +4 -0
- package/rules/abie/ua_http_route/docs/main.md +44 -0
- package/rules/abie/ua_http_route/main.mjs +80 -0
- package/rules/abie/ua_http_route/ua_http_route.mdc +47 -0
- package/rules/abie/ua_node_selector/concern.json +4 -0
- package/rules/abie/ua_node_selector/docs/main.md +34 -0
- package/rules/abie/ua_node_selector/main.mjs +62 -0
- package/rules/abie/ua_node_selector/ua_node_selector.mdc +27 -0
- package/rules/adr/docs/index.md +11 -0
- package/rules/adr/hooks/concern.json +4 -0
- package/rules/adr/hooks/docs/main.md +38 -0
- package/rules/adr/hooks/hooks.mdc +32 -0
- package/rules/adr/hooks/main.mjs +367 -0
- package/rules/adr/main.json +1 -0
- package/rules/adr/main.mdc +93 -0
- package/rules/adr/settings_json/concern.json +9 -0
- package/rules/adr/settings_json/settings_json.mdc +7 -0
- package/rules/adr/settings_json/settings_json.rego +37 -0
- package/rules/adr/settings_local_json/concern.json +9 -0
- package/rules/adr/settings_local_json/settings_local_json.mdc +7 -0
- package/rules/adr/settings_local_json/settings_local_json.rego +40 -0
- package/rules/bun/bunfig/bunfig.mdc +12 -0
- package/rules/bun/bunfig/bunfig.rego +29 -0
- package/rules/bun/bunfig/concern.json +9 -0
- package/rules/bun/bunfig/template/bunfig.toml.snippet.toml +2 -0
- package/rules/bun/docs/index.md +11 -0
- package/rules/bun/layout/concern.json +16 -0
- package/rules/bun/layout/docs/fix-layout.md +29 -0
- package/rules/bun/layout/docs/main.md +34 -0
- package/rules/bun/layout/fix-layout.mjs +63 -0
- package/rules/bun/layout/layout.mdc +60 -0
- package/rules/bun/layout/main.mjs +53 -0
- package/rules/bun/licensee/concern.json +7 -0
- package/rules/bun/licensee/docs/fix-licensee.md +28 -0
- package/rules/bun/licensee/docs/index.md +12 -0
- package/rules/bun/licensee/docs/main.md +37 -0
- package/rules/bun/licensee/fix-licensee.mjs +32 -0
- package/rules/bun/licensee/main.mjs +44 -0
- package/rules/bun/main.json +1 -0
- package/rules/bun/main.mdc +11 -0
- package/rules/bun/package_json/concern.json +9 -0
- package/rules/bun/package_json/docs/fix-package_json.md +28 -0
- package/rules/bun/package_json/docs/index.md +9 -0
- package/rules/bun/package_json/fix-package_json.mjs +311 -0
- package/rules/bun/package_json/package_json.mdc +14 -0
- package/rules/bun/package_json/package_json.rego +64 -0
- package/rules/bun/package_json/template/package.json.deny.json +4 -0
- package/rules/capacitor/docs/index.md +11 -0
- package/rules/capacitor/main.json +1 -0
- package/rules/capacitor/main.mdc +12 -0
- package/rules/capacitor/package_json/concern.json +9 -0
- package/rules/capacitor/package_json/package_json.mdc +9 -0
- package/rules/capacitor/package_json/package_json.rego +45 -0
- package/rules/capacitor/platforms/concern.json +4 -0
- package/rules/capacitor/platforms/docs/main.md +41 -0
- package/rules/capacitor/platforms/main.mjs +486 -0
- package/rules/changelog/consistency/concern.json +4 -0
- package/rules/changelog/consistency/docs/fix-consistency.md +31 -0
- package/rules/changelog/consistency/docs/main.md +41 -0
- package/rules/changelog/consistency/fix-consistency.mjs +71 -0
- package/rules/changelog/consistency/main.mjs +820 -0
- package/rules/changelog/docs/index.md +11 -0
- package/rules/changelog/lib/docs/index.md +11 -0
- package/rules/changelog/lib/docs/package-manifest.md +214 -0
- package/rules/changelog/lib/package-manifest.mjs +158 -0
- package/rules/changelog/main.json +1 -0
- package/rules/changelog/main.mdc +7 -0
- package/rules/changelog/presence/concern.json +7 -0
- package/rules/changelog/presence/docs/main.md +44 -0
- package/rules/changelog/presence/main.mjs +87 -0
- package/rules/ci4/docs/index.md +11 -0
- package/rules/ci4/main.json +1 -0
- package/rules/ci4/main.mdc +375 -0
- package/rules/ci4/marksman_config/concern.json +5 -0
- package/rules/ci4/marksman_config/data/marksman_config/marksman.baseline.toml +27 -0
- package/rules/ci4/marksman_config/docs/fix-marksman_config.md +27 -0
- package/rules/ci4/marksman_config/docs/main.md +30 -0
- package/rules/ci4/marksman_config/fix-marksman_config.mjs +24 -0
- package/rules/ci4/marksman_config/main.mjs +38 -0
- package/rules/ci4/marksman_config/marksman_config.mdc +31 -0
- package/rules/ci4/vscode_extensions/concern.json +9 -0
- package/rules/ci4/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
- package/rules/ci4/vscode_extensions/docs/index.md +11 -0
- package/rules/ci4/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/ci4/vscode_extensions/template/extensions.json.snippet.json +3 -0
- package/rules/ci4/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/ci4/vscode_extensions/vscode_extensions.rego +12 -0
- package/rules/doc-files/check/concern.json +7 -0
- package/rules/doc-files/check/docs/fix-check.md +28 -0
- package/rules/doc-files/check/docs/fix-worker.md +28 -0
- package/rules/doc-files/check/docs/main.md +32 -0
- package/rules/doc-files/check/fix-check.mjs +45 -0
- package/rules/doc-files/check/fix-worker.mjs +56 -0
- package/rules/doc-files/check/main.mjs +99 -0
- package/rules/doc-files/docgen-crc/concern.json +3 -0
- package/rules/doc-files/docgen-crc/docs/main.md +46 -0
- package/rules/doc-files/docgen-crc/main.mjs +207 -0
- package/rules/doc-files/docgen-extract/concern.json +3 -0
- package/rules/doc-files/docgen-extract/docs/main.md +38 -0
- package/rules/doc-files/docgen-extract/main.mjs +471 -0
- package/rules/doc-files/docgen-extract-anchors/concern.json +3 -0
- package/rules/doc-files/docgen-extract-anchors/docs/main.md +49 -0
- package/rules/doc-files/docgen-extract-anchors/main.mjs +115 -0
- package/rules/doc-files/docgen-files-batch/concern.json +3 -0
- package/rules/doc-files/docgen-files-batch/docs/main.md +38 -0
- package/rules/doc-files/docgen-files-batch/main.mjs +517 -0
- package/rules/doc-files/docgen-fix-worker/concern.json +3 -0
- package/rules/doc-files/docgen-gen/concern.json +3 -0
- package/rules/doc-files/docgen-gen/docs/main.md +40 -0
- package/rules/doc-files/docgen-gen/main.mjs +582 -0
- package/rules/doc-files/docgen-ignore/concern.json +3 -0
- package/rules/doc-files/docgen-ignore/docs/main.md +33 -0
- package/rules/doc-files/docgen-ignore/main.mjs +54 -0
- package/rules/doc-files/docgen-judge/concern.json +3 -0
- package/rules/doc-files/docgen-judge/docs/main.md +43 -0
- package/rules/doc-files/docgen-judge/main.mjs +116 -0
- package/rules/doc-files/docgen-prompts/concern.json +3 -0
- package/rules/doc-files/docgen-prompts/docs/main.md +48 -0
- package/rules/doc-files/docgen-prompts/main.mjs +222 -0
- package/rules/doc-files/docgen-scan/concern.json +3 -0
- package/rules/doc-files/docgen-scan/docs/main.md +52 -0
- package/rules/doc-files/docgen-scan/main.mjs +231 -0
- package/rules/doc-files/docs/index.md +11 -0
- package/rules/doc-files/main.json +1 -0
- package/rules/doc-files/main.mdc +49 -0
- package/rules/doc-files/units/concern.json +3 -0
- package/rules/doc-files/units/docs/main.md +32 -0
- package/rules/doc-files/units/main.mjs +20 -0
- package/rules/doc-files/units-js/concern.json +3 -0
- package/rules/doc-files/units-js/docs/main.md +32 -0
- package/rules/doc-files/units-js/main.mjs +141 -0
- package/rules/doc-files/units-rs/concern.json +3 -0
- package/rules/doc-files/units-rs/docs/main.md +36 -0
- package/rules/doc-files/units-rs/main.mjs +311 -0
- package/rules/docker/docs/index.md +11 -0
- package/rules/docker/lib/docker-hadolint.mjs +60 -0
- package/rules/docker/lib/docker-mirror.mjs +148 -0
- package/rules/docker/lib/docker-native-addon.mjs +93 -0
- package/rules/docker/lib/docker-nginx-user.mjs +122 -0
- package/rules/docker/lib/docs/docker-hadolint.md +29 -0
- package/rules/docker/lib/docs/docker-mirror.md +251 -0
- package/rules/docker/lib/docs/docker-native-addon.md +174 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +223 -0
- package/rules/docker/lib/docs/index.md +14 -0
- package/rules/docker/lint/concern.json +7 -0
- package/rules/docker/lint/docs/main.md +47 -0
- package/rules/docker/lint/main.mjs +360 -0
- package/rules/docker/lint_docker_yml/concern.json +9 -0
- package/rules/docker/lint_docker_yml/lint_docker_yml.mdc +14 -0
- package/rules/docker/lint_docker_yml/lint_docker_yml.rego +79 -0
- package/rules/docker/lint_docker_yml/template/lint-docker.yml.snippet.yml +41 -0
- package/rules/docker/main.json +1 -0
- package/rules/docker/main.mdc +14 -0
- package/rules/efes/docs/index.md +11 -0
- package/rules/efes/main.json +1 -0
- package/rules/efes/main.mdc +21 -0
- package/rules/efes/package_json_shared/concern.json +11 -0
- package/rules/efes/package_json_shared/package_json_shared.mdc +33 -0
- package/rules/efes/package_json_shared/package_json_shared.rego +16 -0
- package/rules/feedback/docs/index.md +11 -0
- package/rules/feedback/main.json +1 -0
- package/rules/feedback/main.mdc +63 -0
- package/rules/ga/clean_ga_workflows/clean_ga_workflows.mdc +20 -0
- package/rules/ga/clean_ga_workflows/clean_ga_workflows.rego +112 -0
- package/rules/ga/clean_ga_workflows/concern.json +10 -0
- package/rules/ga/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +26 -0
- package/rules/ga/clean_merged_branch/clean_merged_branch.mdc +24 -0
- package/rules/ga/clean_merged_branch/clean_merged_branch.rego +163 -0
- package/rules/ga/clean_merged_branch/concern.json +10 -0
- package/rules/ga/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +37 -0
- package/rules/ga/docs/index.md +11 -0
- package/rules/ga/git_ai/concern.json +11 -0
- package/rules/ga/git_ai/git_ai.mdc +19 -0
- package/rules/ga/git_ai/git_ai.rego +76 -0
- package/rules/ga/git_ai/template/git-ai.yml.snippet.yml +30 -0
- package/rules/ga/lint_ga/concern.json +10 -0
- package/rules/ga/lint_ga/lint_ga.mdc +21 -0
- package/rules/ga/lint_ga/lint_ga.rego +132 -0
- package/rules/ga/lint_ga/template/lint-ga.yml.snippet.yml +41 -0
- package/rules/ga/main.json +1 -0
- package/rules/ga/main.mdc +17 -0
- package/rules/ga/vscode_extensions/concern.json +11 -0
- package/rules/ga/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
- package/rules/ga/vscode_extensions/docs/index.md +11 -0
- package/rules/ga/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/ga/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/ga/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/ga/vscode_extensions/vscode_extensions.rego +18 -0
- package/rules/ga/vscode_settings/concern.json +11 -0
- package/rules/ga/vscode_settings/template/settings.json.snippet.json +1 -0
- package/rules/ga/vscode_settings/vscode_settings.mdc +9 -0
- package/rules/ga/vscode_settings/vscode_settings.rego +22 -0
- package/rules/ga/workflow_common/concern.json +9 -0
- package/rules/ga/workflow_common/template/uses-min-versions.snippet.json +4 -0
- package/rules/ga/workflow_common/workflow_common.mdc +18 -0
- package/rules/ga/workflow_common/workflow_common.rego +288 -0
- package/rules/ga/workflows/concern.json +8 -0
- package/rules/ga/workflows/docs/fix-workflows.md +37 -0
- package/rules/ga/workflows/docs/main.md +38 -0
- package/rules/ga/workflows/fix-workflows.mjs +212 -0
- package/rules/ga/workflows/main.mjs +433 -0
- package/rules/ga/workflows/workflows.mdc +33 -0
- package/rules/ga/zizmor_yml/concern.json +11 -0
- package/rules/ga/zizmor_yml/template/zizmor.yml.snippet.yml +5 -0
- package/rules/ga/zizmor_yml/zizmor_yml.mdc +9 -0
- package/rules/ga/zizmor_yml/zizmor_yml.rego +27 -0
- package/rules/graphql/docs/index.md +11 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +226 -0
- package/rules/graphql/lib/docs/index.md +11 -0
- package/rules/graphql/lib/graphql-gql-scan.mjs +146 -0
- package/rules/graphql/main.json +1 -0
- package/rules/graphql/main.mdc +8 -0
- package/rules/graphql/tooling/concern.json +8 -0
- package/rules/graphql/tooling/docs/main.md +36 -0
- package/rules/graphql/tooling/main.mjs +129 -0
- package/rules/graphql/tooling/tooling.mdc +13 -0
- package/rules/graphql/vscode_extensions/concern.json +9 -0
- package/rules/graphql/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
- package/rules/graphql/vscode_extensions/docs/index.md +11 -0
- package/rules/graphql/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/graphql/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/graphql/vscode_extensions/vscode_extensions.rego +20 -0
- package/rules/hasura/docs/index.md +11 -0
- package/rules/hasura/internal_urls/concern.json +5 -0
- package/rules/hasura/internal_urls/docs/fix-internal_urls.md +26 -0
- package/rules/hasura/internal_urls/docs/index.md +12 -0
- package/rules/hasura/internal_urls/docs/main.md +36 -0
- package/rules/hasura/internal_urls/fix-internal_urls.mjs +66 -0
- package/rules/hasura/internal_urls/internal_urls.mdc +27 -0
- package/rules/hasura/internal_urls/main.mjs +233 -0
- package/rules/hasura/main.json +1 -0
- package/rules/hasura/main.mdc +8 -0
- package/rules/hasura/migrations/concern.json +4 -0
- package/rules/hasura/migrations/docs/fix-migrations.md +25 -0
- package/rules/hasura/migrations/docs/index.md +12 -0
- package/rules/hasura/migrations/docs/main.md +35 -0
- package/rules/hasura/migrations/fix-migrations.mjs +35 -0
- package/rules/hasura/migrations/main.mjs +53 -0
- package/rules/hasura/migrations/migrations.mdc +13 -0
- package/rules/hasura/svc_hl/concern.json +8 -0
- package/rules/hasura/svc_hl/svc_hl.mdc +15 -0
- package/rules/hasura/svc_hl/svc_hl.rego +50 -0
- package/rules/image-avif/avif_generation/avif_generation.mdc +26 -0
- package/rules/image-avif/avif_generation/concern.json +4 -0
- package/rules/image-avif/avif_generation/docs/fix-avif_generation.md +27 -0
- package/rules/image-avif/avif_generation/docs/main.md +36 -0
- package/rules/image-avif/avif_generation/fix-avif_generation.mjs +80 -0
- package/rules/image-avif/avif_generation/main.mjs +411 -0
- package/rules/image-avif/docs/index.md +11 -0
- package/rules/image-avif/main.json +1 -0
- package/rules/image-avif/main.mdc +8 -0
- package/rules/image-avif/package_json/concern.json +9 -0
- package/rules/image-avif/package_json/package_json.mdc +18 -0
- package/rules/image-avif/package_json/package_json.rego +47 -0
- package/rules/image-avif/package_json/template/package.json.deny.json +5 -0
- package/rules/image-compress/check/concern.json +7 -0
- package/rules/image-compress/check/docs/fix-check.md +34 -0
- package/rules/image-compress/check/docs/index.md +10 -0
- package/rules/image-compress/check/docs/main.md +31 -0
- package/rules/image-compress/check/fix-check.mjs +74 -0
- package/rules/image-compress/check/main.mjs +64 -0
- package/rules/image-compress/docs/index.md +11 -0
- package/rules/image-compress/main.json +1 -0
- package/rules/image-compress/main.mdc +10 -0
- package/rules/image-compress/package_json/concern.json +9 -0
- package/rules/image-compress/package_json/package_json.mdc +13 -0
- package/rules/image-compress/package_json/package_json.rego +21 -0
- package/rules/image-compress/package_json/template/package.json.deny.json +8 -0
- package/rules/image-compress/package_setup/concern.json +4 -0
- package/rules/image-compress/package_setup/docs/main.md +34 -0
- package/rules/image-compress/package_setup/main.mjs +98 -0
- package/rules/image-compress/package_setup/package_setup.mdc +13 -0
- package/rules/js/check/concern.json +19 -0
- package/rules/js/check/docs/eslint-config.md +56 -0
- package/rules/js/check/docs/fix-check.md +31 -0
- package/rules/js/check/docs/index.md +13 -0
- package/rules/js/check/docs/main.md +45 -0
- package/rules/js/check/eslint-config.mjs +262 -0
- package/rules/js/check/fix-check.mjs +39 -0
- package/rules/js/check/main.mjs +312 -0
- package/rules/js/dep-policy/concern.json +4 -0
- package/rules/js/dep-policy/dep-policy.mdc +36 -0
- package/rules/js/dep-policy/docs/main.md +35 -0
- package/rules/js/dep-policy/main.mjs +99 -0
- package/rules/js/docs/index.md +11 -0
- package/rules/js/eslint/concern.json +7 -0
- package/rules/js/eslint/docs/fix-eslint.md +32 -0
- package/rules/js/eslint/docs/main.md +33 -0
- package/rules/js/eslint/fix-eslint.mjs +77 -0
- package/rules/js/eslint/main.mjs +99 -0
- package/rules/js/jscpd_config/concern.json +11 -0
- package/rules/js/jscpd_config/jscpd_config.mdc +14 -0
- package/rules/js/jscpd_config/jscpd_config.rego +44 -0
- package/rules/js/jscpd_config/template/.jscpd.json.snippet.json +7 -0
- package/rules/js/jscpd_duplicates/concern.json +7 -0
- package/rules/js/jscpd_duplicates/docs/main.md +29 -0
- package/rules/js/jscpd_duplicates/main.mjs +70 -0
- package/rules/js/knip/concern.json +6 -0
- package/rules/js/knip/docs/main.md +32 -0
- package/rules/js/knip/main.mjs +68 -0
- package/rules/js/lint-findings/concern.json +3 -0
- package/rules/js/lint-findings/docs/main.md +41 -0
- package/rules/js/lint-findings/main.mjs +125 -0
- package/rules/js/lint_js_yml/concern.json +9 -0
- package/rules/js/lint_js_yml/lint_js_yml.mdc +14 -0
- package/rules/js/lint_js_yml/lint_js_yml.rego +117 -0
- package/rules/js/lint_js_yml/template/lint-js.yml.snippet.yml +40 -0
- package/rules/js/main.json +1 -0
- package/rules/js/main.mdc +19 -0
- package/rules/js/package_json/concern.json +9 -0
- package/rules/js/package_json/package_json.mdc +15 -0
- package/rules/js/package_json/package_json.rego +142 -0
- package/rules/js/package_json/template/package.json.snippet.json +6 -0
- package/rules/js/tooling/concern.json +3 -0
- package/rules/js/tooling/data/tooling/knip-canonical.json +29 -0
- package/rules/js/tooling/data/tooling/oxlint-canonical.json +400 -0
- package/rules/js/tooling/docs/main.md +31 -0
- package/rules/js/tooling/main.mjs +149 -0
- package/rules/js/utils_imports/concern.json +4 -0
- package/rules/js/utils_imports/docs/main.md +50 -0
- package/rules/js/utils_imports/main.mjs +185 -0
- package/rules/js/vscode_extensions/concern.json +11 -0
- package/rules/js/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
- package/rules/js/vscode_extensions/docs/index.md +11 -0
- package/rules/js/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/js/vscode_extensions/template/extensions.json.snippet.json +3 -0
- package/rules/js/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/js/vscode_extensions/vscode_extensions.rego +12 -0
- package/rules/js-bun-db/docs/index.md +11 -0
- package/rules/js-bun-db/lib/bun-sql-scan.mjs +1055 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +63 -0
- package/rules/js-bun-db/lib/docs/index.md +11 -0
- package/rules/js-bun-db/main.json +1 -0
- package/rules/js-bun-db/main.mdc +8 -0
- package/rules/js-bun-db/package_json/concern.json +9 -0
- package/rules/js-bun-db/package_json/package_json.mdc +17 -0
- package/rules/js-bun-db/package_json/package_json.rego +15 -0
- package/rules/js-bun-db/package_json/template/package.json.deny.json +6 -0
- package/rules/js-bun-db/safety/concern.json +4 -0
- package/rules/js-bun-db/safety/docs/main.md +34 -0
- package/rules/js-bun-db/safety/main.mjs +430 -0
- package/rules/js-bun-redis/docs/index.md +11 -0
- package/rules/js-bun-redis/imports/concern.json +4 -0
- package/rules/js-bun-redis/imports/docs/main.md +36 -0
- package/rules/js-bun-redis/imports/imports.mdc +47 -0
- package/rules/js-bun-redis/imports/main.mjs +88 -0
- package/rules/js-bun-redis/lib/docs/index.md +11 -0
- package/rules/js-bun-redis/lib/docs/redis-imports.md +227 -0
- package/rules/js-bun-redis/lib/redis-imports.mjs +130 -0
- package/rules/js-bun-redis/main.json +1 -0
- package/rules/js-bun-redis/main.mdc +8 -0
- package/rules/js-bun-redis/package_json/concern.json +9 -0
- package/rules/js-bun-redis/package_json/package_json.mdc +11 -0
- package/rules/js-bun-redis/package_json/package_json.rego +15 -0
- package/rules/js-bun-redis/package_json/template/package.json.deny.json +12 -0
- package/rules/js-mssql/deps/concern.json +4 -0
- package/rules/js-mssql/deps/docs/main.md +33 -0
- package/rules/js-mssql/deps/main.mjs +297 -0
- package/rules/js-mssql/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/index.md +11 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +380 -0
- package/rules/js-mssql/lib/mssql-pool-scan.mjs +610 -0
- package/rules/js-mssql/main.json +1 -0
- package/rules/js-mssql/main.mdc +14 -0
- package/rules/js-mssql/package_json/concern.json +9 -0
- package/rules/js-mssql/package_json/package_json.mdc +9 -0
- package/rules/js-mssql/package_json/package_json.rego +57 -0
- package/rules/js-run/configmap/concern.json +9 -0
- package/rules/js-run/configmap/configmap.mdc +31 -0
- package/rules/js-run/configmap/configmap.rego +21 -0
- package/rules/js-run/configmap/template/configmap.yaml.contains.yml +4 -0
- package/rules/js-run/docs/index.md +11 -0
- package/rules/js-run/jsconfig/concern.json +9 -0
- package/rules/js-run/jsconfig/docs/fix-jsconfig.md +29 -0
- package/rules/js-run/jsconfig/docs/index.md +9 -0
- package/rules/js-run/jsconfig/fix-jsconfig.mjs +119 -0
- package/rules/js-run/jsconfig/jsconfig.mdc +25 -0
- package/rules/js-run/jsconfig/jsconfig.rego +59 -0
- package/rules/js-run/jsconfig/template/jsconfig.json.snippet.json +10 -0
- package/rules/js-run/lib/bunyan-imports.mjs +98 -0
- package/rules/js-run/lib/check-env-scan.mjs +338 -0
- package/rules/js-run/lib/conn-file-rules.mjs +214 -0
- package/rules/js-run/lib/conn-imports-scan.mjs +154 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +121 -0
- package/rules/js-run/lib/docs/check-env-scan.md +438 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +304 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +208 -0
- package/rules/js-run/lib/docs/index.md +16 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +334 -0
- package/rules/js-run/lib/docs/temporal-scan.md +29 -0
- package/rules/js-run/lib/promise-settimeout-scan.mjs +128 -0
- package/rules/js-run/lib/temporal-scan.mjs +52 -0
- package/rules/js-run/main.json +1 -0
- package/rules/js-run/main.mdc +16 -0
- package/rules/js-run/package_json/concern.json +9 -0
- package/rules/js-run/package_json/package_json.mdc +38 -0
- package/rules/js-run/package_json/package_json.rego +37 -0
- package/rules/js-run/package_json/template/package.json.deny.json +22 -0
- package/rules/js-run/runtime/concern.json +12 -0
- package/rules/js-run/runtime/docs/fix-runtime.md +27 -0
- package/rules/js-run/runtime/docs/main.md +35 -0
- package/rules/js-run/runtime/fix-runtime.mjs +46 -0
- package/rules/js-run/runtime/main.mjs +494 -0
- package/rules/js-run/runtime/runtime.mdc +14 -0
- package/rules/k8s/base_kustomization/base_kustomization.mdc +12 -0
- package/rules/k8s/base_kustomization/base_kustomization.rego +77 -0
- package/rules/k8s/base_kustomization/concern.json +8 -0
- package/rules/k8s/base_manifest/base_manifest.mdc +14 -0
- package/rules/k8s/base_manifest/base_manifest.rego +154 -0
- package/rules/k8s/base_manifest/concern.json +8 -0
- package/rules/k8s/docs/index.md +11 -0
- package/rules/k8s/gateway/concern.json +8 -0
- package/rules/k8s/gateway/gateway.mdc +31 -0
- package/rules/k8s/gateway/gateway.rego +151 -0
- package/rules/k8s/hasura_configmap/concern.json +8 -0
- package/rules/k8s/hasura_configmap/hasura_configmap.mdc +20 -0
- package/rules/k8s/hasura_configmap/hasura_configmap.rego +129 -0
- package/rules/k8s/hasura_httproute/concern.json +8 -0
- package/rules/k8s/hasura_httproute/hasura_httproute.mdc +16 -0
- package/rules/k8s/hasura_httproute/hasura_httproute.rego +298 -0
- package/rules/k8s/hpa_pdb/concern.json +8 -0
- package/rules/k8s/hpa_pdb/hpa_pdb.mdc +25 -0
- package/rules/k8s/hpa_pdb/hpa_pdb.rego +139 -0
- package/rules/k8s/kubeconform/concern.json +7 -0
- package/rules/k8s/kubeconform/docs/main.md +32 -0
- package/rules/k8s/kubeconform/main.mjs +39 -0
- package/rules/k8s/kustomization/concern.json +8 -0
- package/rules/k8s/kustomization/kustomization.mdc +20 -0
- package/rules/k8s/kustomization/kustomization.rego +220 -0
- package/rules/k8s/lint_k8s_yml/concern.json +9 -0
- package/rules/k8s/lint_k8s_yml/lint_k8s_yml.rego +57 -0
- package/rules/k8s/lint_k8s_yml/template/lint-k8s.yml.snippet.yml +43 -0
- package/rules/k8s/main.json +1 -0
- package/rules/k8s/main.mdc +22 -0
- package/rules/k8s/manifest/concern.json +8 -0
- package/rules/k8s/manifest/manifest.mdc +17 -0
- package/rules/k8s/manifest/manifest.rego +328 -0
- package/rules/k8s/manifests/concern.json +6 -0
- package/rules/k8s/manifests/docs/fix-manifests.md +47 -0
- package/rules/k8s/manifests/fix-manifests.mjs +217 -0
- package/rules/k8s/manifests/main.mjs +6838 -0
- package/rules/k8s/network_policy/concern.json +8 -0
- package/rules/k8s/network_policy/network_policy.mdc +22 -0
- package/rules/k8s/network_policy/network_policy.rego +151 -0
- package/rules/k8s/network_policy/template/deployment.snippet.yaml +61 -0
- package/rules/k8s/network_policy/template/stateful-set.snippet.yaml +69 -0
- package/rules/k8s/svc_hl_yaml/concern.json +8 -0
- package/rules/k8s/svc_hl_yaml/svc_hl_yaml.mdc +13 -0
- package/rules/k8s/svc_hl_yaml/svc_hl_yaml.rego +51 -0
- package/rules/k8s/svc_yaml/concern.json +8 -0
- package/rules/k8s/svc_yaml/svc_yaml.mdc +12 -0
- package/rules/k8s/svc_yaml/svc_yaml.rego +31 -0
- package/rules/nginx-default-tpl/docs/index.md +11 -0
- package/rules/nginx-default-tpl/main.json +1 -0
- package/rules/nginx-default-tpl/main.mdc +8 -0
- package/rules/nginx-default-tpl/template/concern.json +17 -0
- package/rules/nginx-default-tpl/template/docs/fix-template.md +26 -0
- package/rules/nginx-default-tpl/template/docs/main.md +42 -0
- package/rules/nginx-default-tpl/template/fix-template.mjs +67 -0
- package/rules/nginx-default-tpl/template/main.mjs +505 -0
- package/rules/nginx-default-tpl/vscode_extensions/concern.json +9 -0
- package/rules/nginx-default-tpl/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/nginx-default-tpl/vscode_extensions/vscode_extensions.rego +16 -0
- package/rules/nginx-default-tpl/vscode_settings/concern.json +9 -0
- package/rules/nginx-default-tpl/vscode_settings/vscode_settings.mdc +15 -0
- package/rules/nginx-default-tpl/vscode_settings/vscode_settings.rego +36 -0
- package/rules/npm-module/docs/index.md +11 -0
- package/rules/npm-module/emit_types_config/concern.json +9 -0
- package/rules/npm-module/emit_types_config/emit_types_config.mdc +43 -0
- package/rules/npm-module/emit_types_config/emit_types_config.rego +28 -0
- package/rules/npm-module/emit_types_config/template/tsconfig.emit-types.json.snippet.json +9 -0
- package/rules/npm-module/header_doc_pointer/concern.json +5 -0
- package/rules/npm-module/header_doc_pointer/docs/main.md +40 -0
- package/rules/npm-module/header_doc_pointer/header_doc_pointer.mdc +18 -0
- package/rules/npm-module/header_doc_pointer/main.mjs +131 -0
- package/rules/npm-module/main.json +1 -0
- package/rules/npm-module/main.mdc +38 -0
- package/rules/npm-module/npm_package_json/concern.json +9 -0
- package/rules/npm-module/npm_package_json/npm_package_json.mdc +57 -0
- package/rules/npm-module/npm_package_json/npm_package_json.rego +73 -0
- package/rules/npm-module/npm_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/npm_publish_yml/concern.json +10 -0
- package/rules/npm-module/npm_publish_yml/template/npm-publish.yml.snippet.yml +49 -0
- package/rules/npm-module/package_structure/concern.json +5 -0
- package/rules/npm-module/package_structure/docs/main.md +37 -0
- package/rules/npm-module/package_structure/main.mjs +515 -0
- package/rules/npm-module/package_structure/package_structure.mdc +63 -0
- package/rules/npm-module/root_package_json/concern.json +9 -0
- package/rules/npm-module/root_package_json/root_package_json.mdc +41 -0
- package/rules/npm-module/root_package_json/root_package_json.rego +28 -0
- package/rules/npm-module/root_package_json/template/package.json.snippet.json +1 -0
- package/rules/npm-module/rule_meta/concern.json +8 -0
- package/rules/npm-module/rule_meta/docs/main.md +35 -0
- package/rules/npm-module/rule_meta/main.mjs +119 -0
- package/rules/npm-module/rule_meta/rule_meta.mdc +11 -0
- package/rules/npm-module/skill_meta/concern.json +5 -0
- package/rules/npm-module/skill_meta/docs/main.md +149 -0
- package/rules/npm-module/skill_meta/main.mjs +91 -0
- package/rules/npm-module/skill_meta/skill_meta.mdc +11 -0
- package/rules/php/cs_fixer/concern.json +7 -0
- package/rules/php/cs_fixer/docs/main.md +34 -0
- package/rules/php/cs_fixer/main.mjs +52 -0
- package/rules/php/docs/index.md +11 -0
- package/rules/php/lint_php_yml/concern.json +9 -0
- package/rules/php/lint_php_yml/lint_php_yml.mdc +23 -0
- package/rules/php/lint_php_yml/lint_php_yml.rego +34 -0
- package/rules/php/lint_php_yml/template/lint-php.yml.snippet.yml +47 -0
- package/rules/php/main.json +1 -0
- package/rules/php/main.mdc +8 -0
- package/rules/php/phpcs/concern.json +7 -0
- package/rules/php/phpcs/docs/main.md +31 -0
- package/rules/php/phpcs/main.mjs +71 -0
- package/rules/php/project/concern.json +6 -0
- package/rules/php/project/docs/main.md +36 -0
- package/rules/php/project/main.mjs +88 -0
- package/rules/php/tooling/concern.json +5 -0
- package/rules/php/tooling/docs/main.md +33 -0
- package/rules/php/tooling/main.mjs +35 -0
- package/rules/php/tooling/tooling.mdc +66 -0
- package/rules/python/applies/concern.json +4 -0
- package/rules/python/applies/docs/main.md +29 -0
- package/rules/python/applies/main.mjs +24 -0
- package/rules/python/docs/index.md +11 -0
- package/rules/python/lib/docs/index.md +9 -0
- package/rules/python/lib/docs/uv-run.md +32 -0
- package/rules/python/lib/uv-run.mjs +48 -0
- package/rules/python/lint_python_yml/concern.json +9 -0
- package/rules/python/lint_python_yml/lint_python_yml.mdc +12 -0
- package/rules/python/lint_python_yml/lint_python_yml.rego +68 -0
- package/rules/python/lint_python_yml/template/lint-python.yml.snippet.yml +41 -0
- package/rules/python/main.json +1 -0
- package/rules/python/main.mdc +8 -0
- package/rules/python/mypy/concern.json +7 -0
- package/rules/python/mypy/docs/main.md +32 -0
- package/rules/python/mypy/main.mjs +36 -0
- package/rules/python/project/concern.json +6 -0
- package/rules/python/project/docs/main.md +43 -0
- package/rules/python/project/main.mjs +115 -0
- package/rules/python/pyproject_toml/concern.json +9 -0
- package/rules/python/pyproject_toml/pyproject_toml.mdc +13 -0
- package/rules/python/pyproject_toml/pyproject_toml.rego +53 -0
- package/rules/python/pyproject_toml/template/pyproject.toml.deny.toml +2 -0
- package/rules/python/pyproject_toml/template/pyproject.toml.snippet.toml +12 -0
- package/rules/python/ruff/concern.json +7 -0
- package/rules/python/ruff/docs/fix-ruff.md +34 -0
- package/rules/python/ruff/docs/main.md +35 -0
- package/rules/python/ruff/fix-ruff.mjs +63 -0
- package/rules/python/ruff/main.mjs +52 -0
- package/rules/python/tooling/concern.json +15 -0
- package/rules/python/tooling/docs/index.md +11 -0
- package/rules/python/tooling/docs/main.md +35 -0
- package/rules/python/tooling/main.mjs +50 -0
- package/rules/python/tooling/tooling.mdc +23 -0
- package/rules/rego/conftest_verify/concern.json +6 -0
- package/rules/rego/conftest_verify/docs/main.md +31 -0
- package/rules/rego/conftest_verify/main.mjs +55 -0
- package/rules/rego/docs/index.md +11 -0
- package/rules/rego/lib/docs/index.md +9 -0
- package/rules/rego/lib/docs/run-external-tool.md +36 -0
- package/rules/rego/lib/run-external-tool.mjs +53 -0
- package/rules/rego/main.json +1 -0
- package/rules/rego/main.mdc +20 -0
- package/rules/rego/opa_check/concern.json +7 -0
- package/rules/rego/opa_check/docs/main.md +32 -0
- package/rules/rego/opa_check/main.mjs +34 -0
- package/rules/rego/package_json/concern.json +10 -0
- package/rules/rego/package_json/package_json.mdc +12 -0
- package/rules/rego/package_json/package_json.rego +21 -0
- package/rules/rego/regal/concern.json +7 -0
- package/rules/rego/regal/docs/index.md +11 -0
- package/rules/rego/regal/docs/main.md +32 -0
- package/rules/rego/regal/main.mjs +34 -0
- package/rules/rego/tooling/concern.json +5 -0
- package/rules/rego/tooling/docs/index.md +11 -0
- package/rules/rego/tooling/docs/main.md +31 -0
- package/rules/rego/tooling/main.mjs +25 -0
- package/rules/rego/tooling/tooling.mdc +14 -0
- package/rules/rego/vscode_extensions/concern.json +11 -0
- package/rules/rego/vscode_extensions/docs/fix-vscode_extensions.md +24 -0
- package/rules/rego/vscode_extensions/docs/index.md +11 -0
- package/rules/rego/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/rego/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/rego/vscode_extensions/vscode_extensions.mdc +11 -0
- package/rules/rego/vscode_extensions/vscode_extensions.rego +15 -0
- package/rules/rego/vscode_settings/concern.json +11 -0
- package/rules/rego/vscode_settings/template/settings.json.snippet.json +6 -0
- package/rules/rego/vscode_settings/vscode_settings.mdc +19 -0
- package/rules/rego/vscode_settings/vscode_settings.rego +30 -0
- package/rules/release/change.mjs +88 -0
- package/rules/release/docs/change.md +189 -0
- package/rules/release/docs/index.md +13 -0
- package/rules/release/docs/release.md +25 -0
- package/rules/release/lib/aggregate.mjs +82 -0
- package/rules/release/lib/change-file.mjs +116 -0
- package/rules/release/lib/docs/aggregate.md +250 -0
- package/rules/release/lib/docs/change-file.md +206 -0
- package/rules/release/lib/docs/fallback.md +207 -0
- package/rules/release/lib/docs/index.md +13 -0
- package/rules/release/lib/fallback.mjs +48 -0
- package/rules/release/main.json +1 -0
- package/rules/release/main.mdc +10 -0
- package/rules/release/release.mjs +220 -0
- package/rules/rust/applies/concern.json +4 -0
- package/rules/rust/applies/docs/index.md +11 -0
- package/rules/rust/applies/docs/main.md +30 -0
- package/rules/rust/applies/main.mjs +28 -0
- package/rules/rust/check/concern.json +7 -0
- package/rules/rust/check/docs/fix-check.md +30 -0
- package/rules/rust/check/docs/index.md +11 -0
- package/rules/rust/check/docs/main.md +37 -0
- package/rules/rust/check/fix-check.mjs +79 -0
- package/rules/rust/check/main.mjs +81 -0
- package/rules/rust/coverage/coverage.mdc +28 -0
- package/rules/rust/docs/index.md +11 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +134 -0
- package/rules/rust/lib/docs/index.md +11 -0
- package/rules/rust/lib/has-cargo-toml.mjs +37 -0
- package/rules/rust/lint_rust_yml/concern.json +11 -0
- package/rules/rust/lint_rust_yml/lint_rust_yml.mdc +17 -0
- package/rules/rust/lint_rust_yml/lint_rust_yml.rego +79 -0
- package/rules/rust/lint_rust_yml/template/lint-rust.yml.snippet.yml +44 -0
- package/rules/rust/main.json +1 -0
- package/rules/rust/main.mdc +8 -0
- package/rules/rust/package_json/concern.json +10 -0
- package/rules/rust/package_json/package_json.mdc +12 -0
- package/rules/rust/package_json/package_json.rego +20 -0
- package/rules/rust/toolchain_cache/concern.json +5 -0
- package/rules/rust/toolchain_cache/docs/fix-toolchain_cache.md +35 -0
- package/rules/rust/toolchain_cache/docs/index.md +12 -0
- package/rules/rust/toolchain_cache/docs/main.md +57 -0
- package/rules/rust/toolchain_cache/fix-toolchain_cache.mjs +117 -0
- package/rules/rust/toolchain_cache/main.mjs +177 -0
- package/rules/rust/toolchain_cache/toolchain_cache.mdc +23 -0
- package/rules/rust/vscode_extensions/concern.json +11 -0
- package/rules/rust/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
- package/rules/rust/vscode_extensions/docs/index.md +11 -0
- package/rules/rust/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/rust/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/rust/vscode_extensions/vscode_extensions.mdc +9 -0
- package/rules/rust/vscode_extensions/vscode_extensions.rego +15 -0
- package/rules/security/docs/index.md +11 -0
- package/rules/security/lint_security_yml/concern.json +11 -0
- package/rules/security/lint_security_yml/lint_security_yml.mdc +7 -0
- package/rules/security/lint_security_yml/lint_security_yml.rego +33 -0
- package/rules/security/lint_security_yml/template/lint-security.yml.snippet.yml +31 -0
- package/rules/security/main.json +1 -0
- package/rules/security/main.mdc +15 -0
- package/rules/security/package_json/concern.json +9 -0
- package/rules/security/package_json/package_json.mdc +7 -0
- package/rules/security/package_json/package_json.rego +19 -0
- package/rules/security/package_json/template/package.json.deny.json +4 -0
- package/rules/security/sample_secret/concern.json +11 -0
- package/rules/security/sample_secret/docs/index.md +11 -0
- package/rules/security/sample_secret/docs/main.md +29 -0
- package/rules/security/sample_secret/main.mjs +80 -0
- package/rules/security/sample_secret/sample_secret.mdc +20 -0
- package/rules/security/scan/concern.json +6 -0
- package/rules/security/scan/docs/index.md +11 -0
- package/rules/security/scan/docs/main.md +32 -0
- package/rules/security/scan/main.mjs +48 -0
- package/rules/security/todo.MD +27 -0
- package/rules/security/trufflehog/concern.json +4 -0
- package/rules/security/trufflehog/docs/index.md +11 -0
- package/rules/security/trufflehog/docs/main.md +28 -0
- package/rules/security/trufflehog/main.mjs +45 -0
- package/rules/security/trufflehog/templates/trufflehog/.trufflehog-exclude.snippet.txt +6 -0
- package/rules/security/trufflehog/trufflehog.mdc +21 -0
- package/rules/style/docs/index.md +11 -0
- package/rules/style/lint/concern.json +7 -0
- package/rules/style/lint/docs/fix-lint.md +29 -0
- package/rules/style/lint/docs/index.md +11 -0
- package/rules/style/lint/docs/main.md +29 -0
- package/rules/style/lint/fix-lint.mjs +66 -0
- package/rules/style/lint/main.mjs +68 -0
- package/rules/style/lint_style_yml/concern.json +9 -0
- package/rules/style/lint_style_yml/lint_style_yml.mdc +13 -0
- package/rules/style/lint_style_yml/lint_style_yml.rego +38 -0
- package/rules/style/lint_style_yml/template/lint-style.yml.snippet.yml +39 -0
- package/rules/style/main.json +1 -0
- package/rules/style/main.mdc +13 -0
- package/rules/style/package_json/concern.json +9 -0
- package/rules/style/package_json/package_json.mdc +18 -0
- package/rules/style/package_json/package_json.rego +31 -0
- package/rules/style/package_json/template/package.json.snippet.json +5 -0
- package/rules/style/tooling/concern.json +14 -0
- package/rules/style/tooling/docs/fix-tooling.md +29 -0
- package/rules/style/tooling/docs/index.md +12 -0
- package/rules/style/tooling/docs/main.md +34 -0
- package/rules/style/tooling/fix-tooling.mjs +58 -0
- package/rules/style/tooling/main.mjs +78 -0
- package/rules/style/tooling/tooling.mdc +85 -0
- package/rules/style/vscode_extensions/concern.json +9 -0
- package/rules/style/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
- package/rules/style/vscode_extensions/docs/index.md +11 -0
- package/rules/style/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/style/vscode_extensions/template/extensions.json.snippet.json +1 -0
- package/rules/style/vscode_extensions/vscode_extensions.mdc +13 -0
- package/rules/style/vscode_extensions/vscode_extensions.rego +13 -0
- package/rules/style/vscode_settings/concern.json +9 -0
- package/rules/style/vscode_settings/template/settings.json.snippet.json +5 -0
- package/rules/style/vscode_settings/vscode_settings.mdc +19 -0
- package/rules/style/vscode_settings/vscode_settings.rego +15 -0
- package/rules/tauri/cargo_mutants_config/cargo_mutants_config.mdc +39 -0
- package/rules/tauri/cargo_mutants_config/concern.json +5 -0
- package/rules/tauri/cargo_mutants_config/docs/fix-cargo_mutants_config.md +30 -0
- package/rules/tauri/cargo_mutants_config/docs/index.md +12 -0
- package/rules/tauri/cargo_mutants_config/docs/main.md +30 -0
- package/rules/tauri/cargo_mutants_config/fix-cargo_mutants_config.mjs +53 -0
- package/rules/tauri/cargo_mutants_config/main.mjs +148 -0
- package/rules/tauri/core_test_isolation/concern.json +5 -0
- package/rules/tauri/core_test_isolation/core_test_isolation.mdc +43 -0
- package/rules/tauri/core_test_isolation/docs/index.md +9 -0
- package/rules/tauri/core_test_isolation/docs/main.md +36 -0
- package/rules/tauri/core_test_isolation/main.mjs +166 -0
- package/rules/tauri/docs/index.md +11 -0
- package/rules/tauri/linux_deps/concern.json +8 -0
- package/rules/tauri/linux_deps/docs/fix-linux_deps.md +29 -0
- package/rules/tauri/linux_deps/docs/index.md +10 -0
- package/rules/tauri/linux_deps/docs/main.md +36 -0
- package/rules/tauri/linux_deps/fix-linux_deps.mjs +89 -0
- package/rules/tauri/linux_deps/linux_deps.mdc +18 -0
- package/rules/tauri/linux_deps/main.mjs +100 -0
- package/rules/tauri/main.json +1 -0
- package/rules/tauri/main.mdc +86 -0
- package/rules/tauri/release/concern.json +8 -0
- package/rules/tauri/release/docs/fix-release.md +28 -0
- package/rules/tauri/release/docs/index.md +10 -0
- package/rules/tauri/release/docs/main.md +32 -0
- package/rules/tauri/release/fix-release.mjs +353 -0
- package/rules/tauri/release/main.mjs +315 -0
- package/rules/tauri/release/release.mdc +104 -0
- package/rules/tauri/tooling/concern.json +8 -0
- package/rules/tauri/tooling/docs/index.md +11 -0
- package/rules/tauri/tooling/docs/main.md +34 -0
- package/rules/tauri/tooling/main.mjs +94 -0
- package/rules/tauri/tooling/tooling.mdc +27 -0
- package/rules/tauri/updater/concern.json +14 -0
- package/rules/tauri/updater/docs/fix-updater.md +33 -0
- package/rules/tauri/updater/docs/index.md +10 -0
- package/rules/tauri/updater/docs/main.md +32 -0
- package/rules/tauri/updater/fix-updater.mjs +322 -0
- package/rules/tauri/updater/main.mjs +403 -0
- package/rules/tauri/updater/updater.mdc +90 -0
- package/rules/tauri/vscode_extensions/concern.json +9 -0
- package/rules/tauri/vscode_extensions/docs/fix-vscode_extensions.md +25 -0
- package/rules/tauri/vscode_extensions/docs/index.md +11 -0
- package/rules/tauri/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/tauri/vscode_extensions/vscode_extensions.mdc +23 -0
- package/rules/tauri/vscode_extensions/vscode_extensions.rego +26 -0
- package/rules/test/cargo_mutants_config/cargo_mutants_config.mdc +18 -0
- package/rules/test/cargo_mutants_config/concern.json +8 -0
- package/rules/test/cargo_mutants_config/data/cargo_mutants_config/mutants.toml.baseline +7 -0
- package/rules/test/cargo_mutants_config/docs/fix-cargo_mutants_config.md +31 -0
- package/rules/test/cargo_mutants_config/docs/index.md +12 -0
- package/rules/test/cargo_mutants_config/docs/main.md +36 -0
- package/rules/test/cargo_mutants_config/fix-cargo_mutants_config.mjs +49 -0
- package/rules/test/cargo_mutants_config/main.mjs +62 -0
- package/rules/test/docs/index.md +11 -0
- package/rules/test/location/concern.json +8 -0
- package/rules/test/location/docs/index.md +11 -0
- package/rules/test/location/docs/main.md +36 -0
- package/rules/test/location/location.mdc +52 -0
- package/rules/test/location/main.mjs +70 -0
- package/rules/test/main.json +1 -0
- package/rules/test/main.mdc +26 -0
- package/rules/test/no-bun-test-import/concern.json +7 -0
- package/rules/test/no-bun-test-import/docs/fix-no-bun-test-import.md +31 -0
- package/rules/test/no-bun-test-import/docs/index.md +10 -0
- package/rules/test/no-bun-test-import/docs/main.md +36 -0
- package/rules/test/no-bun-test-import/fix-no-bun-test-import.mjs +51 -0
- package/rules/test/no-bun-test-import/main.mjs +105 -0
- package/rules/test/no-bun-test-import/no-bun-test-import.mdc +9 -0
- package/rules/test/no-console-store-restore/concern.json +7 -0
- package/rules/test/no-console-store-restore/docs/index.md +11 -0
- package/rules/test/no-console-store-restore/docs/main.md +44 -0
- package/rules/test/no-console-store-restore/main.mjs +88 -0
- package/rules/test/no-console-store-restore/no-console-store-restore.mdc +11 -0
- package/rules/test/no-process-chdir/concern.json +7 -0
- package/rules/test/no-process-chdir/docs/index.md +11 -0
- package/rules/test/no-process-chdir/docs/main.md +32 -0
- package/rules/test/no-process-chdir/main.mjs +40 -0
- package/rules/test/no-process-chdir/no-process-chdir.mdc +15 -0
- package/rules/test/no-relative-fs-path/concern.json +7 -0
- package/rules/test/no-relative-fs-path/docs/index.md +11 -0
- package/rules/test/no-relative-fs-path/docs/main.md +34 -0
- package/rules/test/no-relative-fs-path/main.mjs +239 -0
- package/rules/test/no-relative-fs-path/no-relative-fs-path.mdc +22 -0
- package/rules/test/package_json/concern.json +11 -0
- package/rules/test/package_json/package_json.mdc +18 -0
- package/rules/test/package_json/package_json.rego +25 -0
- package/rules/test/package_json/template/package.json.contains.json +6 -0
- package/rules/test/sandbox-aware-test/concern.json +7 -0
- package/rules/test/sandbox-aware-test/docs/index.md +11 -0
- package/rules/test/sandbox-aware-test/docs/main.md +55 -0
- package/rules/test/sandbox-aware-test/main.mjs +89 -0
- package/rules/test/sandbox-aware-test/sandbox-aware-test.mdc +28 -0
- package/rules/test/stryker_config/concern.json +8 -0
- package/rules/test/stryker_config/data/stryker_config/docs/index.md +13 -0
- package/rules/test/stryker_config/data/stryker_config/docs/stryker-vue-macros-ignorer.md +31 -0
- package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.baseline.md +31 -0
- package/rules/test/stryker_config/data/stryker_config/docs/stryker.config.vue.baseline.md +35 -0
- package/rules/test/stryker_config/data/stryker_config/stryker-vue-macros-ignorer.mjs +48 -0
- package/rules/test/stryker_config/data/stryker_config/stryker.config.baseline.mjs +18 -0
- package/rules/test/stryker_config/data/stryker_config/stryker.config.vue.baseline.mjs +23 -0
- package/rules/test/stryker_config/data/vitest_config/docs/index.md +11 -0
- package/rules/test/stryker_config/data/vitest_config/docs/vitest.config.baseline.md +35 -0
- package/rules/test/stryker_config/data/vitest_config/vitest.config.baseline.js +22 -0
- package/rules/test/stryker_config/docs/fix-stryker_config.md +42 -0
- package/rules/test/stryker_config/docs/index.md +12 -0
- package/rules/test/stryker_config/docs/main.md +38 -0
- package/rules/test/stryker_config/fix-stryker_config.mjs +76 -0
- package/rules/test/stryker_config/main.mjs +504 -0
- package/rules/test/stryker_config/stryker_config.mdc +26 -0
- package/rules/test/vitest-config-pool-forks/concern.json +7 -0
- package/rules/test/vitest-config-pool-forks/docs/index.md +11 -0
- package/rules/test/vitest-config-pool-forks/docs/main.md +42 -0
- package/rules/test/vitest-config-pool-forks/main.mjs +41 -0
- package/rules/test/vitest-config-pool-forks/vitest-config-pool-forks.mdc +34 -0
- package/rules/text/cspell/concern.json +9 -0
- package/rules/text/cspell/cspell.mdc +41 -0
- package/rules/text/cspell/cspell.rego +71 -0
- package/rules/text/cspell/docs/fix-cspell.md +36 -0
- package/rules/text/cspell/docs/index.md +11 -0
- package/rules/text/cspell/fix-cspell.mjs +115 -0
- package/rules/text/cspell/template/.cspell.json.contains.json +3 -0
- package/rules/text/cspell/template/.cspell.json.deny.json +5 -0
- package/rules/text/cspell/template/.cspell.json.snippet.json +15 -0
- package/rules/text/cspell-fix/concern.json +7 -0
- package/rules/text/cspell-fix/docs/fix-worker.md +38 -0
- package/rules/text/cspell-fix/docs/index.md +12 -0
- package/rules/text/cspell-fix/docs/main.md +32 -0
- package/rules/text/cspell-fix/fix-worker.mjs +73 -0
- package/rules/text/cspell-fix/main.mjs +185 -0
- package/rules/text/docs/index.md +11 -0
- package/rules/text/forbidden-prettier/concern.json +7 -0
- package/rules/text/forbidden-prettier/docs/index.md +11 -0
- package/rules/text/forbidden-prettier/docs/main.md +38 -0
- package/rules/text/forbidden-prettier/forbidden-prettier.mdc +14 -0
- package/rules/text/forbidden-prettier/main.mjs +56 -0
- package/rules/text/formatting/concern.json +17 -0
- package/rules/text/formatting/docs/index.md +11 -0
- package/rules/text/formatting/docs/main.md +39 -0
- package/rules/text/formatting/main.mjs +149 -0
- package/rules/text/lint_text/concern.json +8 -0
- package/rules/text/lint_text/lint_text.mdc +19 -0
- package/rules/text/lint_text/lint_text.rego +104 -0
- package/rules/text/lint_text/template/lint-text.yml.snippet.yml +61 -0
- package/rules/text/main.json +1 -0
- package/rules/text/main.mdc +23 -0
- package/rules/text/markdownlint/concern.json +12 -0
- package/rules/text/markdownlint/docs/fix-markdownlint.md +38 -0
- package/rules/text/markdownlint/docs/index.md +12 -0
- package/rules/text/markdownlint/docs/main.md +40 -0
- package/rules/text/markdownlint/fix-markdownlint.mjs +64 -0
- package/rules/text/markdownlint/main.mjs +55 -0
- package/rules/text/markdownlint/markdownlint.mdc +40 -0
- package/rules/text/markdownlint/markdownlint.rego +57 -0
- package/rules/text/markdownlint/template/.markdownlint-cli2.jsonc.snippet.jsonc +11 -0
- package/rules/text/oxfmt/concern.json +7 -0
- package/rules/text/oxfmt/docs/fix-oxfmt.md +28 -0
- package/rules/text/oxfmt/docs/main.md +27 -0
- package/rules/text/oxfmt/fix-oxfmt.mjs +52 -0
- package/rules/text/oxfmt/main.mjs +48 -0
- package/rules/text/oxfmt/oxfmt.mdc +11 -0
- package/rules/text/oxfmtrc/concern.json +9 -0
- package/rules/text/oxfmtrc/oxfmtrc.mdc +11 -0
- package/rules/text/oxfmtrc/oxfmtrc.rego +55 -0
- package/rules/text/oxfmtrc/template/.oxfmtrc.json.snippet.json +8 -0
- package/rules/text/package_json/concern.json +9 -0
- package/rules/text/package_json/package_json.mdc +35 -0
- package/rules/text/package_json/package_json.rego +66 -0
- package/rules/text/package_json/template/package.json.deny.json +15 -0
- package/rules/text/run-dotenv-linter/concern.json +7 -0
- package/rules/text/run-dotenv-linter/docs/fix-run-dotenv-linter.md +43 -0
- package/rules/text/run-dotenv-linter/docs/index.md +12 -0
- package/rules/text/run-dotenv-linter/docs/main.md +35 -0
- package/rules/text/run-dotenv-linter/fix-run-dotenv-linter.mjs +64 -0
- package/rules/text/run-dotenv-linter/main.mjs +122 -0
- package/rules/text/run-shellcheck/concern.json +7 -0
- package/rules/text/run-shellcheck/docs/fix-run-shellcheck.md +40 -0
- package/rules/text/run-shellcheck/docs/index.md +12 -0
- package/rules/text/run-shellcheck/docs/main.md +31 -0
- package/rules/text/run-shellcheck/fix-run-shellcheck.mjs +44 -0
- package/rules/text/run-shellcheck/main.mjs +245 -0
- package/rules/text/run-v8r/concern.json +7 -0
- package/rules/text/run-v8r/docs/index.md +11 -0
- package/rules/text/run-v8r/docs/main.md +40 -0
- package/rules/text/run-v8r/main.mjs +266 -0
- package/rules/text/vscode_extensions/concern.json +9 -0
- package/rules/text/vscode_extensions/docs/fix-vscode_extensions.md +36 -0
- package/rules/text/vscode_extensions/docs/index.md +11 -0
- package/rules/text/vscode_extensions/fix-vscode_extensions.mjs +1 -0
- package/rules/text/vscode_extensions/template/extensions.json.snippet.json +3 -0
- package/rules/text/vscode_extensions/vscode_extensions.mdc +13 -0
- package/rules/text/vscode_extensions/vscode_extensions.rego +12 -0
- package/rules/text/vscode_settings/concern.json +9 -0
- package/rules/text/vscode_settings/template/settings.json.snippet.json +9 -0
- package/rules/text/vscode_settings/vscode_settings.mdc +13 -0
- package/rules/text/vscode_settings/vscode_settings.rego +34 -0
- package/rules/tool-surface/docs/index.md +11 -0
- package/rules/tool-surface/main.json +6 -0
- package/rules/tool-surface/main.mdc +72 -0
- package/rules/vue/docs/index.md +11 -0
- package/rules/vue/lib/docs/index.md +11 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +265 -0
- package/rules/vue/lib/vue-forbidden-imports.mjs +264 -0
- package/rules/vue/main.json +1 -0
- package/rules/vue/main.mdc +18 -0
- package/rules/vue/package_json/concern.json +9 -0
- package/rules/vue/package_json/package_json.mdc +30 -0
- package/rules/vue/package_json/package_json.rego +140 -0
- package/rules/vue/packages/concern.json +6 -0
- package/rules/vue/packages/docs/index.md +11 -0
- package/rules/vue/packages/docs/main.md +35 -0
- package/rules/vue/packages/main.mjs +575 -0
- package/rules/worktree/docs/index.md +11 -0
- package/rules/worktree/main.json +1 -0
- package/rules/worktree/main.mdc +100 -0
- package/rules/worktree/vscode_settings/concern.json +10 -0
- package/rules/worktree/vscode_settings/template/settings.json.snippet.json +8 -0
- package/rules/worktree/zed_settings/concern.json +10 -0
- package/rules/worktree/zed_settings/template/settings.json.snippet.json +12 -0
- package/schemas/any.json +4 -0
- package/schemas/concern.json +108 -0
- package/schemas/layers-config.json +38 -0
- package/schemas/n-rules.json +65 -0
- package/schemas/rule-meta.json +39 -0
- package/schemas/skill-meta.json +30 -0
- package/schemas/target.json +63 -0
- package/schemas/v8r-catalog.json +179 -0
- package/schemas/vendor/bunfig.json +616 -0
- package/schemas/vendor/cargo-mutants-config.json +158 -0
- package/schemas/vendor/cargo.json +9438 -0
- package/schemas/vendor/claude-code-settings.json +2742 -0
- package/schemas/vendor/codex-hooks.json +148 -0
- package/schemas/vendor/cspell.json +2427 -0
- package/schemas/vendor/github-action.json +695 -0
- package/schemas/vendor/github-workflow.json +2193 -0
- package/schemas/vendor/jsconfig.json +1252 -0
- package/schemas/vendor/jscpd.json +405 -0
- package/schemas/vendor/knip.json +915 -0
- package/schemas/vendor/mcp.json +26 -0
- package/schemas/vendor/mt-config.json +85 -0
- package/schemas/vendor/oxfmtrc.json +810 -0
- package/schemas/vendor/oxlintrc.json +18690 -0
- package/schemas/vendor/package.json +23798 -0
- package/schemas/vendor/prettierrc.json +433 -0
- package/schemas/vendor/quikrun.json +195 -0
- package/schemas/vendor/schema-catalog.json +62 -0
- package/schemas/vendor/tsconfig.json +1535 -0
- package/schemas/vendor/zizmor.json +489 -0
- package/scripts/auto-rules.mjs +444 -0
- package/scripts/auto-skills.mjs +95 -0
- package/scripts/build-agents-commands.mjs +91 -0
- package/scripts/cli-entry.mjs +34 -0
- package/scripts/dispatcher/docs/index.md +11 -0
- package/scripts/docs/auto-rules.md +34 -0
- package/scripts/docs/auto-skills.md +30 -0
- package/scripts/docs/build-agents-commands.md +37 -0
- package/scripts/docs/cli-entry.md +36 -0
- package/scripts/docs/ensure-n-rules-dev-dependencies.md +40 -0
- package/scripts/docs/hook.md +30 -0
- package/scripts/docs/index.md +22 -0
- package/scripts/docs/post-tool-use-check.md +27 -0
- package/scripts/docs/rename-yaml-extensions.md +40 -0
- package/scripts/docs/skills-cli.md +35 -0
- package/scripts/docs/smoke-check-imports.md +31 -0
- package/scripts/docs/sync-claude-config.md +84 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +28 -0
- package/scripts/docs/update-blue-oak.md +28 -0
- package/scripts/docs/upgrade-n-rules-and-install.md +36 -0
- package/scripts/ensure-n-rules-dev-dependencies.mjs +217 -0
- package/scripts/hook.mjs +100 -0
- package/scripts/lib/adr/docs/index.md +12 -0
- package/scripts/lib/adr/docs/normalize-cli.md +36 -0
- package/scripts/lib/adr/docs/normalize-pipeline.md +45 -0
- package/scripts/lib/adr/normalize-cli.mjs +74 -0
- package/scripts/lib/adr/normalize-pipeline.mjs +701 -0
- package/scripts/lib/assert-project-root.mjs +77 -0
- package/scripts/lib/blue-oak.mjs +41 -0
- package/scripts/lib/changed-files.mjs +83 -0
- package/scripts/lib/check-reporter.mjs +27 -0
- package/scripts/lib/collect-test-files.mjs +48 -0
- package/scripts/lib/concern-meta.mjs +140 -0
- package/scripts/lib/diff-added-lines.mjs +86 -0
- package/scripts/lib/discover-check-rules-from-cursor.mjs +40 -0
- package/scripts/lib/discover-checkable-rules.mjs +49 -0
- package/scripts/lib/docs/assert-project-root.md +30 -0
- package/scripts/lib/docs/blue-oak.md +29 -0
- package/scripts/lib/docs/changed-files.md +32 -0
- package/scripts/lib/docs/check-reporter.md +179 -0
- package/scripts/lib/docs/collect-test-files.md +32 -0
- package/scripts/lib/docs/concern-meta.md +34 -0
- package/scripts/lib/docs/diff-added-lines.md +38 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +29 -0
- package/scripts/lib/docs/discover-checkable-rules.md +25 -0
- package/scripts/lib/docs/ensure-tool.md +258 -0
- package/scripts/lib/docs/generated-markdown.md +279 -0
- package/scripts/lib/docs/gha-workflow.md +38 -0
- package/scripts/lib/docs/index.md +40 -0
- package/scripts/lib/docs/inline-template-links.md +25 -0
- package/scripts/lib/docs/list-project-rules-mdc.md +27 -0
- package/scripts/lib/docs/list-rule-ids.md +25 -0
- package/scripts/lib/docs/load-cursor-config.md +154 -0
- package/scripts/lib/docs/mirror-parity.md +32 -0
- package/scripts/lib/docs/read-n-rules-config-lite.md +26 -0
- package/scripts/lib/docs/resolve-target-files.md +38 -0
- package/scripts/lib/docs/root-notice.md +29 -0
- package/scripts/lib/docs/rule-meta-helpers.md +38 -0
- package/scripts/lib/docs/rule-meta.md +29 -0
- package/scripts/lib/docs/rule-predicates.md +31 -0
- package/scripts/lib/docs/run-conftest-batch.md +30 -0
- package/scripts/lib/docs/run-lint-step.md +29 -0
- package/scripts/lib/docs/run-standard-lint.md +43 -0
- package/scripts/lib/docs/skill-meta.md +62 -0
- package/scripts/lib/docs/sync-gitignore-worktree.md +26 -0
- package/scripts/lib/docs/template.md +44 -0
- package/scripts/lib/docs/timing-summary.md +25 -0
- package/scripts/lib/docs/workspaces.md +34 -0
- package/scripts/lib/docs/worktree-notice.md +27 -0
- package/scripts/lib/ensure-tool.mjs +352 -0
- package/scripts/lib/fix/docs/index.md +11 -0
- package/scripts/lib/fix/docs/vscode-ext-add.md +33 -0
- package/scripts/lib/fix/vscode-ext-add.mjs +53 -0
- package/scripts/lib/generated-markdown.mjs +71 -0
- package/scripts/lib/gha-workflow.mjs +220 -0
- package/scripts/lib/inline-template-links.mjs +108 -0
- package/scripts/lib/lint-surface/codegen-opa-wrapper.mjs +30 -0
- package/scripts/lib/lint-surface/collateral-veto.mjs +55 -0
- package/scripts/lib/lint-surface/default-worker.mjs +67 -0
- package/scripts/lib/lint-surface/detect.mjs +169 -0
- package/scripts/lib/lint-surface/docs/codegen-opa-wrapper.md +30 -0
- package/scripts/lib/lint-surface/docs/collateral-veto.md +28 -0
- package/scripts/lib/lint-surface/docs/default-worker.md +32 -0
- package/scripts/lib/lint-surface/docs/detect.md +38 -0
- package/scripts/lib/lint-surface/docs/index.md +27 -0
- package/scripts/lib/lint-surface/docs/ladder.md +33 -0
- package/scripts/lib/lint-surface/docs/lint-lock.md +47 -0
- package/scripts/lib/lint-surface/docs/mt-run-node.md +37 -0
- package/scripts/lib/lint-surface/docs/mt-tail.md +41 -0
- package/scripts/lib/lint-surface/docs/policy-lint-adapter.md +44 -0
- package/scripts/lib/lint-surface/docs/policy-test-step.md +43 -0
- package/scripts/lib/lint-surface/docs/progress.md +28 -0
- package/scripts/lib/lint-surface/docs/render.md +29 -0
- package/scripts/lib/lint-surface/docs/run-detectors.md +32 -0
- package/scripts/lib/lint-surface/docs/run-fix.md +33 -0
- package/scripts/lib/lint-surface/docs/snapshot.md +30 -0
- package/scripts/lib/lint-surface/docs/tier-sampling-bench.md +40 -0
- package/scripts/lib/lint-surface/docs/tier-sampling-experiment.md +35 -0
- package/scripts/lib/lint-surface/docs/types.md +27 -0
- package/scripts/lib/lint-surface/docs/violation-reporter.md +32 -0
- package/scripts/lib/lint-surface/ladder.mjs +92 -0
- package/scripts/lib/lint-surface/lint-lock.mjs +247 -0
- package/scripts/lib/lint-surface/mt-run-node.mjs +206 -0
- package/scripts/lib/lint-surface/mt-tail.mjs +0 -0
- package/scripts/lib/lint-surface/policy-lint-adapter.mjs +101 -0
- package/scripts/lib/lint-surface/policy-test-step.mjs +149 -0
- package/scripts/lib/lint-surface/progress.mjs +198 -0
- package/scripts/lib/lint-surface/render.mjs +50 -0
- package/scripts/lib/lint-surface/run-detectors.mjs +314 -0
- package/scripts/lib/lint-surface/run-fix.mjs +732 -0
- package/scripts/lib/lint-surface/snapshot.mjs +83 -0
- package/scripts/lib/lint-surface/tier-sampling-bench.mjs +457 -0
- package/scripts/lib/lint-surface/tier-sampling-experiment.mjs +289 -0
- package/scripts/lib/lint-surface/types.mjs +150 -0
- package/scripts/lib/lint-surface/violation-reporter.mjs +46 -0
- package/scripts/lib/list-project-rules-mdc.mjs +22 -0
- package/scripts/lib/list-rule-ids.mjs +32 -0
- package/scripts/lib/load-cursor-config.mjs +58 -0
- package/scripts/lib/mirror-parity.mjs +62 -0
- package/scripts/lib/read-n-rules-config-lite.mjs +63 -0
- package/scripts/lib/resolve-target-files.mjs +103 -0
- package/scripts/lib/root-notice.mjs +67 -0
- package/scripts/lib/rule-meta-helpers.mjs +103 -0
- package/scripts/lib/rule-meta.mjs +66 -0
- package/scripts/lib/rule-predicates.mjs +114 -0
- package/scripts/lib/run-conftest-batch.mjs +124 -0
- package/scripts/lib/run-lint-step.mjs +38 -0
- package/scripts/lib/run-standard-lint.mjs +50 -0
- package/scripts/lib/skill-meta.mjs +90 -0
- package/scripts/lib/sync-gitignore-worktree.mjs +27 -0
- package/scripts/lib/template.mjs +315 -0
- package/scripts/lib/timing-summary.mjs +63 -0
- package/scripts/lib/workspaces.mjs +109 -0
- package/scripts/lib/worktree-notice.mjs +183 -0
- package/scripts/post-tool-use-check.mjs +82 -0
- package/scripts/rename-yaml-extensions.mjs +170 -0
- package/scripts/skills-cli.mjs +262 -0
- package/scripts/smoke-check-imports.mjs +66 -0
- package/scripts/sync-claude-config.mjs +692 -0
- package/scripts/sync-setup-bun-deps-action.mjs +36 -0
- package/scripts/update-blue-oak.mjs +50 -0
- package/scripts/upgrade-n-rules-and-install.mjs +264 -0
- package/scripts/utils/apply-to-files.mjs +36 -0
- package/scripts/utils/ast-extract.mjs +149 -0
- package/scripts/utils/ast-scan-utils.mjs +206 -0
- package/scripts/utils/docs/apply-to-files.md +38 -0
- package/scripts/utils/docs/ast-extract.md +30 -0
- package/scripts/utils/docs/ast-scan-utils.md +54 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +32 -0
- package/scripts/utils/docs/find-package-json-paths.md +30 -0
- package/scripts/utils/docs/index.md +23 -0
- package/scripts/utils/docs/lock-cache-dir.md +29 -0
- package/scripts/utils/docs/pass.md +29 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +27 -0
- package/scripts/utils/docs/resolve-cmd.md +33 -0
- package/scripts/utils/docs/resolve-js-root.md +26 -0
- package/scripts/utils/docs/test-helpers.md +30 -0
- package/scripts/utils/docs/walk-cache.md +31 -0
- package/scripts/utils/docs/walkDir.md +31 -0
- package/scripts/utils/docs/with-lock.md +30 -0
- package/scripts/utils/docs/worktree-fingerprint.md +31 -0
- package/scripts/utils/ensure-gitignore-entries.mjs +30 -0
- package/scripts/utils/find-package-json-paths.mjs +30 -0
- package/scripts/utils/lock-cache-dir.mjs +37 -0
- package/scripts/utils/pass.mjs +9 -0
- package/scripts/utils/resolve-cargo-manifest.mjs +62 -0
- package/scripts/utils/resolve-cmd.mjs +25 -0
- package/scripts/utils/resolve-js-root.mjs +61 -0
- package/scripts/utils/walk-cache.mjs +24 -0
- package/scripts/utils/walkDir.mjs +55 -0
- package/scripts/utils/with-lock.mjs +190 -0
- package/scripts/utils/worktree-fingerprint.mjs +33 -0
- package/skills/adr-normalize/SKILL.md +73 -0
- package/skills/adr-normalize/main.json +1 -0
- package/skills/brainstorming/SKILL.md +165 -0
- package/skills/brainstorming/main.json +1 -0
- package/skills/doc-aggregate/SKILL.md +121 -0
- package/skills/doc-aggregate/js/docs/index.md +11 -0
- package/skills/doc-aggregate/main.json +1 -0
- package/skills/doc-files/.changes/260612-0002.md +6 -0
- package/skills/doc-files/.changes/260612-0006.md +6 -0
- package/skills/doc-files/.changes/260612-0008.md +6 -0
- package/skills/doc-files/.changes/260612-0012.md +6 -0
- package/skills/doc-files/.changes/260612-0031.md +6 -0
- package/skills/doc-files/.changes/260612-0036.md +6 -0
- package/skills/doc-files/.changes/260612-0114.md +6 -0
- package/skills/doc-files/SKILL.md +86 -0
- package/skills/doc-files/main.json +1 -0
- package/skills/lint/SKILL.md +121 -0
- package/skills/lint/main.json +1 -0
- package/skills/llm-patch/SKILL.md +260 -0
- package/skills/llm-patch/main.json +1 -0
- package/skills/publish-telegram/SKILL.md +93 -0
- package/skills/publish-telegram/main.json +1 -0
- package/skills/taze/SKILL.md +114 -0
- package/skills/taze/js/diff.mjs +140 -0
- package/skills/taze/js/docs/diff.md +55 -0
- package/skills/taze/js/docs/index.md +11 -0
- package/skills/taze/main.json +1 -0
- package/types/bin/n-rules.d.ts +2 -0
package/bin/n-rules.js
ADDED
|
@@ -0,0 +1,1709 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* n-rules — CLI завантаження правил та перевірки проєкту
|
|
5
|
+
*
|
|
6
|
+
* Використання:
|
|
7
|
+
* `npx \@7n/rules` — завантажити cursor-правила (синк); якщо в корені вже є `.n-rules.json`,
|
|
8
|
+
* спочатку зчитується конфіг і за потреби дописується `$schema`
|
|
9
|
+
* `npx \@7n/rules rename-yaml-extensions` — k8s `*.yml` → `*.yaml`, `.github` `*.yaml` → `*.yml` (опції: `--dry-run`, `--root=…`; див. bin/rename-yaml-extensions.mjs)
|
|
10
|
+
* `npx \@7n/rules hook --post-tool-use` — PostToolUse hook: per-file lint правила для зміненого файлу (stdin JSON `tool_input.file_path`). Прописується автоматично в `.claude/settings.json`.
|
|
11
|
+
* `npx \@7n/rules hook --stop` — Stop hook: per-file lint по всіх змінених файлах (git diff HEAD + untracked).
|
|
12
|
+
* `npx \@7n/rules lint` — data-driven оркестратор lint+конформності по `rules/<id>/meta.json` (`lint: per-file|full`):
|
|
13
|
+
* за замовчуванням fix-by-default по дельті vs origin (лише `per-file` правила); `--full` =
|
|
14
|
+
* весь репо (`per-file` ∪ `full`); `--no-fix` = без мутацій/LLM (CI); позиційні
|
|
15
|
+
* (не-флаг) аргументи — фільтр правил конформності (мапить колишній `fix <rule>`).
|
|
16
|
+
* CI = `lint --no-fix --full` (весь репо, нуль мутацій/LLM).
|
|
17
|
+
* `npx \@7n/rules skill list` — скіли пакета без синку в проєкт
|
|
18
|
+
* `npx \@7n/rules skill taze` — промпт на stdout
|
|
19
|
+
* `npx \@7n/rules skill cursor taze ["task"]` — Cursor CLI (`cursor-agent -p`)
|
|
20
|
+
* `npx \@7n/rules skill claude taze ["task"]` — Claude Code CLI (`claude -p`)
|
|
21
|
+
*
|
|
22
|
+
* Agent інтеграція: під час синку, окрім `.cursor/rules` і `.claude/commands` (з skills), CLI ще раз
|
|
23
|
+
* синхронізує `.claude/settings.json` (hooks + permissions; merge — користувацькі поля зберігаються)
|
|
24
|
+
* і `.cursor/hooks.json` (Cursor Agent hooks; merge — користувацькі hooks зберігаються).
|
|
25
|
+
* Опт-аут — поле `claude-config: false` у `.n-rules.json`.
|
|
26
|
+
* Pi.dev інтеграція: для кожного skill у `.cursor/skills/<dir>/` CLI генерує
|
|
27
|
+
* `.pi/skills/<dir>/SKILL.md` із frontmatter `name`+`description` (формат pi.dev). Тіло — делегат
|
|
28
|
+
* на джерельний `.cursor/skills/<dir>/SKILL.md`. Always-on, симетрично до `.claude/commands/`.
|
|
29
|
+
*
|
|
30
|
+
* Якщо у корені репозиторію немає .n-rules.json, спочатку перейменовується за наявності nitra-cursor.json;
|
|
31
|
+
* у `.cursor/rules` файли `nitra-*.mdc` перейменовуються на `n-*.mdc`; інакше конфіг створюється автоматично
|
|
32
|
+
* з усіма правилами з каталогу rules/ пакету (їх можна відредагувати після створення). У файлі завжди має бути
|
|
33
|
+
* поле `$schema` з посиланням на JSON Schema пакету (публічний URL для IDE); при зчитуванні конфігу воно додається або виправляється на диску, якщо відсутнє або некоректне.
|
|
34
|
+
* Масиви `rules`, `skills`, `disable-rules` і `disable-skills` при записі сортуються за алфавітом.
|
|
35
|
+
*
|
|
36
|
+
* Файл AGENTS.md у корені: щоразу повністю перезаписується змістом з AGENTS.template.md
|
|
37
|
+
* пакету; список правил у шаблоні будується з файлів *.mdc у .cursor/rules поточного проєкту.
|
|
38
|
+
* Секція команд — з кореневого package.json (scripts) та фіксовані рядки про CLI синхрону/перевірок.
|
|
39
|
+
*
|
|
40
|
+
* Після завантаження: у .cursor/rules видаляються файли *.mdc з префіксом «n-» (керовані
|
|
41
|
+
* пакетом), яких немає у списку rules у .n-rules.json. Інші .mdc у цій директорії залишаються.
|
|
42
|
+
*
|
|
43
|
+
* Composite GitHub Action `.github/actions/setup-bun-deps/action.yml` копіюється з каталогу
|
|
44
|
+
* `github-actions/` пакету при кожному успішному синку (workflows з правил ga / js-lint / text).
|
|
45
|
+
*
|
|
46
|
+
* Skills копіюються з npm/skills пакету лише для id з масиву «skills» у .n-rules.json
|
|
47
|
+
* (у JSON — без префікса, як імена каталогів у rules/ без n-). У пакеті джерело — каталоги
|
|
48
|
+
* skills/<id>/ (без префікса); у проєкті — .cursor/skills/n-<id>/ (префікс n-, як n-*.mdc).
|
|
49
|
+
* Якщо ключа skills немає, за замовчуванням підтягуються всі підкаталоги skills/ (лише імена без префікса n-).
|
|
50
|
+
* Зайві каталоги n-* у .cursor/skills, яких немає у списку, видаляються.
|
|
51
|
+
* Файл `auto.md` у скілі — джерело правди для auto-skills у CLI (`scripts/auto-skills.mjs`)
|
|
52
|
+
* і у проєкт не копіюється; раніше синхронізовані `auto.md` у `.cursor/skills/n-<id>/` CLI
|
|
53
|
+
* не чіпає — їх потрібно прибрати вручну.
|
|
54
|
+
*
|
|
55
|
+
* Якщо в корені є package.json і в ньому ще немає \@7n/rules у devDependencies (і не оголошено
|
|
56
|
+
* в dependencies), CLI дописує devDependencies з діапазоном ^<version> поточного пакету — зручно після npx.
|
|
57
|
+
*
|
|
58
|
+
* Перед копіюванням правил (режим без підкоманди): оновлення \@7n/rules у package.json до
|
|
59
|
+
* останньої версії з npm (крім workspace:/file:/link: тощо), `bun i`, далі файли беруться з
|
|
60
|
+
* `node_modules/@7n/rules`, якщо пакет з’явився після встановлення.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import { spawnSync } from 'node:child_process'
|
|
64
|
+
import { existsSync } from 'node:fs'
|
|
65
|
+
import { mkdir, readdir, readFile, rename, rm, unlink, writeFile } from 'node:fs/promises'
|
|
66
|
+
import { basename, dirname, join, resolve } from 'node:path'
|
|
67
|
+
import { cwd, env } from 'node:process'
|
|
68
|
+
import { fileURLToPath } from 'node:url'
|
|
69
|
+
|
|
70
|
+
import { buildAgentsCommandBulletItems } from '../scripts/build-agents-commands.mjs'
|
|
71
|
+
import { formatGeneratedMarkdownLines, renderAgentsTemplate } from '../scripts/lib/generated-markdown.mjs'
|
|
72
|
+
import { appendDiscoveredMdcFiles, inlineTemplateLinks } from '../scripts/lib/inline-template-links.mjs'
|
|
73
|
+
import {
|
|
74
|
+
detectAutoRules,
|
|
75
|
+
detectLegacyRuleIds,
|
|
76
|
+
mergeConfigWithAutoDetected,
|
|
77
|
+
normalizeIdList,
|
|
78
|
+
RULE_MIGRATIONS
|
|
79
|
+
} from '../scripts/auto-rules.mjs'
|
|
80
|
+
import { detectAutoSkills } from '../scripts/auto-skills.mjs'
|
|
81
|
+
import { readSkillMetaRaw } from '../scripts/lib/skill-meta.mjs'
|
|
82
|
+
import { injectWorktreeNotice } from '../scripts/lib/worktree-notice.mjs'
|
|
83
|
+
import { injectRootNotice } from '../scripts/lib/root-notice.mjs'
|
|
84
|
+
import { listProjectRulesMdcFiles } from '../scripts/lib/list-project-rules-mdc.mjs'
|
|
85
|
+
import { ensureNRulesInRootDevDependencies } from '../scripts/ensure-n-rules-dev-dependencies.mjs'
|
|
86
|
+
import { assertCwdIsProjectRoot } from '../scripts/lib/assert-project-root.mjs'
|
|
87
|
+
import { syncClaudeConfig } from '../scripts/sync-claude-config.mjs'
|
|
88
|
+
import { syncGitignoreWorktree } from '../scripts/lib/sync-gitignore-worktree.mjs'
|
|
89
|
+
import { upgradeNRulesToLatestAndBunInstall } from '../scripts/upgrade-n-rules-and-install.mjs'
|
|
90
|
+
import { runRenameYamlExtensionsCli } from './rename-yaml-extensions.mjs'
|
|
91
|
+
import { runSkillsCli } from '../scripts/skills-cli.mjs'
|
|
92
|
+
import { syncSetupBunDepsAction } from '../scripts/sync-setup-bun-deps-action.mjs'
|
|
93
|
+
|
|
94
|
+
const PACKAGE_NAME = '@7n/rules'
|
|
95
|
+
const CONFIG_FILE = '.n-rules.json'
|
|
96
|
+
/** Публічний URL JSON Schema для поля `$schema` у `.n-rules.json` (IDE); вміст правил CLI читає лише з диска пакету */
|
|
97
|
+
const CONFIG_SCHEMA_URL = 'https://unpkg.com/@7n/rules/schemas/n-rules.json'
|
|
98
|
+
const AGENTS_FILE = 'AGENTS.md'
|
|
99
|
+
const AGENTS_TEMPLATE_FILE = 'AGENTS.template.md'
|
|
100
|
+
const RULES_DIR = '.cursor/rules'
|
|
101
|
+
const SKILLS_DIR = '.cursor/skills'
|
|
102
|
+
const COMMANDS_DIR = '.claude/commands'
|
|
103
|
+
const PI_SKILLS_DIR = '.pi/skills'
|
|
104
|
+
const RULE_PREFIX = 'n-'
|
|
105
|
+
|
|
106
|
+
const binDir = dirname(fileURLToPath(import.meta.url))
|
|
107
|
+
const BUNDLED_RULES_DIR = join(binDir, '..', 'rules')
|
|
108
|
+
const BUNDLED_SKILLS_DIR = join(binDir, '..', 'skills')
|
|
109
|
+
const BUNDLED_AGENTS_TEMPLATE_PATH = join(binDir, '..', AGENTS_TEMPLATE_FILE)
|
|
110
|
+
/** Корінь установленого пакету (каталог з `rules/`, `github-actions/`, …) */
|
|
111
|
+
const BUNDLED_PACKAGE_ROOT = join(binDir, '..')
|
|
112
|
+
|
|
113
|
+
const YAML_FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/
|
|
114
|
+
const NEWLINE_RE = /\r?\n/
|
|
115
|
+
const LEADING_SPACES_RE = /^\s+/
|
|
116
|
+
|
|
117
|
+
/** Ключі `.n-rules.json`, де значення — масиви id; після запуску CLI сортуються за алфавітом */
|
|
118
|
+
const CONFIG_SORTED_ARRAY_KEYS = /** @type {const} */ (['rules', 'skills', 'disable-rules', 'disable-skills'])
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Сортує масиви id у конфігу за алфавітом (`localeCompare`), щоб порядок у файлі був стабільним після синку.
|
|
122
|
+
* @param {Record<string, unknown>} config об'єкт конфігу перед записом на диск
|
|
123
|
+
* @returns {Record<string, unknown>} копія з відсортованими масивами для відомих ключів
|
|
124
|
+
*/
|
|
125
|
+
function sortConfigIdArrays(config) {
|
|
126
|
+
const out = { ...config }
|
|
127
|
+
for (const key of CONFIG_SORTED_ARRAY_KEYS) {
|
|
128
|
+
const v = out[key]
|
|
129
|
+
if (key in out && Array.isArray(v)) {
|
|
130
|
+
out[key] = v.map(String).toSorted((a, b) => a.localeCompare(b))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Імена правил з каталогу `rules/` поточної інсталяції пакету. Кожне правило — окремий
|
|
138
|
+
* підкаталог `rules/<id>/`, у якому має бути `main.mdc`.
|
|
139
|
+
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету
|
|
140
|
+
* @returns {Promise<string[]>} відсортовані id правил (імена підкаталогів)
|
|
141
|
+
*/
|
|
142
|
+
async function discoverBundledRuleNames(bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
143
|
+
if (!existsSync(bundledRulesDir)) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Не знайдено каталог правил пакету.\n` +
|
|
146
|
+
`Очікуваний шлях: ${bundledRulesDir}\n` +
|
|
147
|
+
`Перевстановіть ${PACKAGE_NAME} або створіть ${CONFIG_FILE} вручну.`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
const entries = await readdir(bundledRulesDir, { withFileTypes: true })
|
|
151
|
+
const rules = entries
|
|
152
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
153
|
+
.filter(e => existsSync(join(bundledRulesDir, e.name, 'main.mdc')))
|
|
154
|
+
.map(e => e.name)
|
|
155
|
+
.toSorted((a, b) => a.localeCompare(b))
|
|
156
|
+
if (rules.length === 0) {
|
|
157
|
+
throw new Error(`У каталозі rules/ пакету немає підкаталогів з main.mdc. Створіть ${CONFIG_FILE} вручну.`)
|
|
158
|
+
}
|
|
159
|
+
return rules
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Імена skills (id без префікса n-) з каталогу skills пакету — лише підкаталоги `<id>/` без префікса n-
|
|
164
|
+
* @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету
|
|
165
|
+
* @returns {Promise<string[]>} відсортовані id
|
|
166
|
+
*/
|
|
167
|
+
async function discoverBundledSkillNames(bundledSkillsDir = BUNDLED_SKILLS_DIR) {
|
|
168
|
+
if (!existsSync(bundledSkillsDir)) {
|
|
169
|
+
return []
|
|
170
|
+
}
|
|
171
|
+
const entries = await readdir(bundledSkillsDir, { withFileTypes: true })
|
|
172
|
+
return entries
|
|
173
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith(RULE_PREFIX))
|
|
174
|
+
.map(e => e.name)
|
|
175
|
+
.toSorted((a, b) => a.localeCompare(b))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Перейменовує у каталозі правил файли `nitra-*.mdc` → `n-*.mdc`. Якщо `n-*.mdc` уже є, застарілий файл видаляється.
|
|
180
|
+
* @param {string} rulesDir абсолютний шлях до `.cursor/rules`
|
|
181
|
+
* @returns {Promise<void>}
|
|
182
|
+
*/
|
|
183
|
+
async function migrateLegacyManagedRuleFilenames(rulesDir) {
|
|
184
|
+
if (!existsSync(rulesDir)) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
const names = await readdir(rulesDir)
|
|
188
|
+
for (const name of names) {
|
|
189
|
+
if (!(name.endsWith('.mdc') && name.startsWith('nitra-'))) {
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const rest = name.slice('nitra-'.length)
|
|
194
|
+
const newName = `${RULE_PREFIX}${rest}`
|
|
195
|
+
const from = join(rulesDir, name)
|
|
196
|
+
const to = join(rulesDir, newName)
|
|
197
|
+
if (existsSync(to)) {
|
|
198
|
+
await unlink(from)
|
|
199
|
+
console.log(`📝 Видалено застарілий ${RULES_DIR}/${name} (вже є ${newName})\n`)
|
|
200
|
+
} else {
|
|
201
|
+
await rename(from, to)
|
|
202
|
+
console.log(`📝 Перейменовано ${RULES_DIR}/${name} → ${RULES_DIR}/${newName}\n`)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Міграція legacy: `nitra-*.mdc` → `n-*.mdc` у `.cursor/rules`; якщо немає `.n-rules.json`, конфіг
|
|
209
|
+
* береться з legacy-імен (`.n-rules.json`, потім `nitra-cursor.json`) з переписуванням `$schema` на новий URL
|
|
210
|
+
* @returns {Promise<void>}
|
|
211
|
+
*/
|
|
212
|
+
async function migrateLegacyConfigIfNeeded() {
|
|
213
|
+
const root = cwd()
|
|
214
|
+
await migrateLegacyManagedRuleFilenames(join(root, RULES_DIR))
|
|
215
|
+
|
|
216
|
+
const target = join(root, CONFIG_FILE)
|
|
217
|
+
if (existsSync(target)) {
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
for (const legacyName of ['.n-cursor.json', 'nitra-cursor.json']) {
|
|
221
|
+
const legacyPath = join(root, legacyName)
|
|
222
|
+
if (!existsSync(legacyPath)) {
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
await rename(legacyPath, target)
|
|
226
|
+
console.log(`📝 Перейменовано ${legacyName} → ${CONFIG_FILE}\n`)
|
|
227
|
+
try {
|
|
228
|
+
const parsed = JSON.parse(await readFile(target, 'utf8'))
|
|
229
|
+
if (typeof parsed?.$schema === 'string' && parsed.$schema.includes('@nitra/cursor')) {
|
|
230
|
+
parsed.$schema = CONFIG_SCHEMA_URL
|
|
231
|
+
await writeFile(target, `${JSON.stringify(parsed, null, 2)}\n`)
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
/* некоректний JSON — $schema виправить подальший readConfig */
|
|
235
|
+
}
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Повертає розпарсений package.json з кореня або null, якщо файл відсутній/некоректний.
|
|
242
|
+
* @returns {Promise<unknown | null>} обʼєкт package.json або null
|
|
243
|
+
*/
|
|
244
|
+
async function readRootPackageJsonSafe() {
|
|
245
|
+
const packageJsonPath = join(cwd(), 'package.json')
|
|
246
|
+
if (!existsSync(packageJsonPath)) {
|
|
247
|
+
return null
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return JSON.parse(await readFile(packageJsonPath, 'utf8'))
|
|
251
|
+
} catch {
|
|
252
|
+
return null
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Зчитує конфіг .n-rules.json з поточної директорії
|
|
258
|
+
* @param {{ bundledRulesDir?: string, bundledSkillsDir?: string }} [paths] каталоги з пакету-джерела (після `bun i` — зазвичай `node_modules/@7n/rules`)
|
|
259
|
+
* @returns {Promise<{ $schema: string, rules: string[], skills: string[], version?: string } & Record<string, unknown>>} rules, skills (id без префікса n-); поле version у файлі за наявності ігнорується при синхронізації правил
|
|
260
|
+
*/
|
|
261
|
+
async function readConfig(paths = {}) {
|
|
262
|
+
const bundledRulesDir = paths.bundledRulesDir ?? BUNDLED_RULES_DIR
|
|
263
|
+
const bundledSkillsDir = paths.bundledSkillsDir ?? BUNDLED_SKILLS_DIR
|
|
264
|
+
await migrateLegacyConfigIfNeeded()
|
|
265
|
+
const configPath = join(cwd(), CONFIG_FILE)
|
|
266
|
+
const availableRules = await discoverBundledRuleNames(bundledRulesDir)
|
|
267
|
+
const availableSkills = await discoverBundledSkillNames(bundledSkillsDir)
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Автодописує правила/skills за `rules/<rule>/auto.md` і синхронізує `$schema`.
|
|
271
|
+
* @param {Record<string, unknown>} parsedConfig сирий обʼєкт конфігу
|
|
272
|
+
* @returns {Promise<Record<string, unknown>>} нормалізований конфіг
|
|
273
|
+
*/
|
|
274
|
+
async function normalizeConfigWithAutoRules(parsedConfig) {
|
|
275
|
+
const currentRules = parsedConfig.rules
|
|
276
|
+
if (!Array.isArray(currentRules)) {
|
|
277
|
+
throw new TypeError(`У ${CONFIG_FILE} поле "rules" має бути масивом рядків`)
|
|
278
|
+
}
|
|
279
|
+
if ('skills' in parsedConfig && !Array.isArray(parsedConfig.skills)) {
|
|
280
|
+
throw new Error(`У ${CONFIG_FILE} поле "skills" має бути масивом рядків`)
|
|
281
|
+
}
|
|
282
|
+
const { ignore } = parsedConfig
|
|
283
|
+
if (ignore !== undefined && (!Array.isArray(ignore) || ignore.some(p => typeof p !== 'string'))) {
|
|
284
|
+
throw new Error(`У ${CONFIG_FILE} поле "ignore" має бути масивом рядків (шляхів до директорій)`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const rootPkg = await readRootPackageJsonSafe()
|
|
288
|
+
const disableRules = normalizeIdList(parsedConfig['disable-rules'])
|
|
289
|
+
const disableSkills = normalizeIdList(parsedConfig['disable-skills'])
|
|
290
|
+
const autoDetectedRules = await detectAutoRules({
|
|
291
|
+
root: cwd(),
|
|
292
|
+
availableRules,
|
|
293
|
+
packageJsonParsed: rootPkg,
|
|
294
|
+
disableRules
|
|
295
|
+
})
|
|
296
|
+
// Skills залежать від ефективного списку правил, який буде у конфізі після merge:
|
|
297
|
+
// вже існуючі (опт-ін вручну) + auto-detected, мінус `disable-rules`. Без цього
|
|
298
|
+
// правило, додане вручну (напр. `adr` без auto.md-умови), не активувало б залежні
|
|
299
|
+
// скіли (`adr-normalize`).
|
|
300
|
+
const disableRulesSet = new Set(disableRules)
|
|
301
|
+
const effectiveRulesForSkills = [
|
|
302
|
+
...new Set([...normalizeIdList(parsedConfig.rules), ...autoDetectedRules.rules]).difference(disableRulesSet)
|
|
303
|
+
]
|
|
304
|
+
const autoDetectedSkills = detectAutoSkills({
|
|
305
|
+
availableSkills,
|
|
306
|
+
detectedRules: effectiveRulesForSkills,
|
|
307
|
+
disableSkills
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
const merged = mergeConfigWithAutoDetected({
|
|
311
|
+
config: parsedConfig,
|
|
312
|
+
detectedRules: autoDetectedRules.rules,
|
|
313
|
+
detectedSkills: autoDetectedSkills.skills,
|
|
314
|
+
availableRules,
|
|
315
|
+
availableSkills
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
if (merged.pruned) {
|
|
319
|
+
const parts = []
|
|
320
|
+
if (merged.pruned.rules.length > 0) parts.push(`rules: ${merged.pruned.rules.join(', ')}`)
|
|
321
|
+
if (merged.pruned.skills.length > 0) parts.push(`skills: ${merged.pruned.skills.join(', ')}`)
|
|
322
|
+
console.log(`🧹 Прибрано з ${CONFIG_FILE} неактуальні (немає у пакеті) — ${parts.join('; ')}\n`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const rest = Object.fromEntries(Object.entries(parsedConfig).filter(([k]) => k !== '$schema'))
|
|
326
|
+
const normalized = {
|
|
327
|
+
$schema: CONFIG_SCHEMA_URL,
|
|
328
|
+
...rest,
|
|
329
|
+
rules: merged.rules,
|
|
330
|
+
skills: merged.skills
|
|
331
|
+
}
|
|
332
|
+
if (merged['disable-rules']?.length) {
|
|
333
|
+
normalized['disable-rules'] = merged['disable-rules']
|
|
334
|
+
}
|
|
335
|
+
if (merged['disable-skills']?.length) {
|
|
336
|
+
normalized['disable-skills'] = merged['disable-skills']
|
|
337
|
+
}
|
|
338
|
+
return sortConfigIdArrays(normalized)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (!existsSync(configPath)) {
|
|
342
|
+
const rootPkg = await readRootPackageJsonSafe()
|
|
343
|
+
const autoDetectedRules = await detectAutoRules({
|
|
344
|
+
root: cwd(),
|
|
345
|
+
availableRules,
|
|
346
|
+
packageJsonParsed: rootPkg
|
|
347
|
+
})
|
|
348
|
+
const autoDetectedSkills = detectAutoSkills({
|
|
349
|
+
availableSkills,
|
|
350
|
+
detectedRules: autoDetectedRules.rules
|
|
351
|
+
})
|
|
352
|
+
const defaultConfig = sortConfigIdArrays({
|
|
353
|
+
$schema: CONFIG_SCHEMA_URL,
|
|
354
|
+
rules: autoDetectedRules.rules,
|
|
355
|
+
skills: autoDetectedSkills.skills
|
|
356
|
+
})
|
|
357
|
+
await writeFile(configPath, `${JSON.stringify(defaultConfig, null, 2)}\n`, 'utf8')
|
|
358
|
+
console.log(
|
|
359
|
+
`📝 Створено ${CONFIG_FILE} з автоаналізом правил (${defaultConfig.rules.length}) і skills (${defaultConfig.skills.length}).\n`
|
|
360
|
+
)
|
|
361
|
+
return defaultConfig
|
|
362
|
+
}
|
|
363
|
+
const raw = await readFile(configPath, 'utf8')
|
|
364
|
+
/** @type {Record<string, unknown>} */
|
|
365
|
+
let config
|
|
366
|
+
try {
|
|
367
|
+
config = JSON.parse(raw)
|
|
368
|
+
} catch {
|
|
369
|
+
throw new Error(`Невірний JSON у файлі ${CONFIG_FILE}`)
|
|
370
|
+
}
|
|
371
|
+
logRuleMigrationsIfAny(config)
|
|
372
|
+
const normalized = await normalizeConfigWithAutoRules(config)
|
|
373
|
+
if (JSON.stringify(normalized) !== JSON.stringify(config)) {
|
|
374
|
+
await writeFile(configPath, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
|
|
375
|
+
console.log(`📝 Оновлено ${CONFIG_FILE}: синхронізовано $schema та авто-додані rules/skills\n`)
|
|
376
|
+
}
|
|
377
|
+
return normalized
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Якщо у `rules` чи `disable-rules` є застарілі rule-id з `RULE_MIGRATIONS`,
|
|
382
|
+
* виводить пояснювальний лог про автоматичну заміну (саму заміну виконує
|
|
383
|
+
* `migrateRuleIds` у `mergeConfigWithAutoDetected` — тут лише користувацька комунікація).
|
|
384
|
+
* @param {Record<string, unknown>} parsedConfig сирий обʼєкт `.n-rules.json` після `JSON.parse`
|
|
385
|
+
* @returns {void}
|
|
386
|
+
*/
|
|
387
|
+
function logRuleMigrationsIfAny(parsedConfig) {
|
|
388
|
+
/** @type {Set<string>} */
|
|
389
|
+
const seen = new Set()
|
|
390
|
+
for (const key of /** @type {const} */ (['rules', 'disable-rules'])) {
|
|
391
|
+
const list = parsedConfig[key]
|
|
392
|
+
if (!Array.isArray(list)) continue
|
|
393
|
+
const legacy = detectLegacyRuleIds(normalizeIdList(list))
|
|
394
|
+
for (const id of legacy) seen.add(id)
|
|
395
|
+
}
|
|
396
|
+
if (seen.size === 0) return
|
|
397
|
+
console.log(`📦 Авто-міграція ${CONFIG_FILE}:`)
|
|
398
|
+
for (const id of seen) {
|
|
399
|
+
const replacement = RULE_MIGRATIONS[id].join(', ')
|
|
400
|
+
console.log(` • ${id} → ${replacement}`)
|
|
401
|
+
}
|
|
402
|
+
console.log('')
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Витягує чистий id правила без шляху і без .mdc.
|
|
407
|
+
* "npm/rules/text/text.mdc" → "text"
|
|
408
|
+
* "text.mdc" → "text"
|
|
409
|
+
* "text" → "text"
|
|
410
|
+
* @param {string} ruleName шлях або базове ім'я, з суфіксом .mdc або без
|
|
411
|
+
* @returns {string} id правила (без .mdc, без шляху)
|
|
412
|
+
*/
|
|
413
|
+
function normalizeRuleName(ruleName) {
|
|
414
|
+
const name = basename(String(ruleName).trim())
|
|
415
|
+
return name.endsWith('.mdc') ? name.slice(0, -'.mdc'.length) : name
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Читає вміст правила з каталогу `rules/<id>/main.mdc` установленого пакету
|
|
420
|
+
* (наприклад `node_modules/@7n/rules/rules/<id>/main.mdc` або кеш npx).
|
|
421
|
+
* @param {string} rule елемент масиву rules з `.n-rules.json`
|
|
422
|
+
* @param {string} [bundledRulesDir] каталог `rules/` у корені пакету-джерела
|
|
423
|
+
* @returns {Promise<string>} текст правила для запису в `.cursor/rules/n-*.mdc`
|
|
424
|
+
*/
|
|
425
|
+
async function readBundledRuleContent(rule, bundledRulesDir = BUNDLED_RULES_DIR) {
|
|
426
|
+
const id = normalizeRuleName(rule)
|
|
427
|
+
const bundledPath = join(bundledRulesDir, id, 'main.mdc')
|
|
428
|
+
if (!existsSync(bundledPath)) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`Немає файлу ${id}/main.mdc у ${bundledRulesDir}. Оновіть ${PACKAGE_NAME} або приберіть "${rule}" з rules у ${CONFIG_FILE}.`
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
const text = await readFile(bundledPath, 'utf8')
|
|
434
|
+
const withTemplates = await inlineTemplateLinks(text, dirname(bundledPath))
|
|
435
|
+
return appendDiscoveredMdcFiles(withTemplates, dirname(bundledPath))
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Нормалізує id skill з конфігу до форми без префікса n- (як «fix»)
|
|
440
|
+
* @param {string} skillName елемент масиву skills або ім'я каталогу
|
|
441
|
+
* @returns {string} id без префікса n-
|
|
442
|
+
*/
|
|
443
|
+
function normalizeSkillId(skillName) {
|
|
444
|
+
let s = basename(String(skillName).trim())
|
|
445
|
+
if (s.startsWith(RULE_PREFIX)) {
|
|
446
|
+
s = s.slice(RULE_PREFIX.length)
|
|
447
|
+
}
|
|
448
|
+
return s
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Ім'я керованого каталогу skill у .cursor/skills (префікс n-)
|
|
453
|
+
* @param {string} skillId id без префікса (або з префіксом n- у конфігу — нормалізується)
|
|
454
|
+
* @returns {string} наприклад n-fix
|
|
455
|
+
*/
|
|
456
|
+
function managedSkillDirName(skillId) {
|
|
457
|
+
return `${RULE_PREFIX}${normalizeSkillId(skillId)}`
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Витягує текст description з YAML frontmatter SKILL.md (формат description: >-)
|
|
462
|
+
* @param {string} text повний вміст SKILL.md
|
|
463
|
+
* @returns {string | null} один рядок опису або null
|
|
464
|
+
*/
|
|
465
|
+
function extractSkillDescription(text) {
|
|
466
|
+
const fm = text.match(YAML_FRONTMATTER_RE)
|
|
467
|
+
if (!fm) {
|
|
468
|
+
return null
|
|
469
|
+
}
|
|
470
|
+
const lines = fm[1].split(NEWLINE_RE)
|
|
471
|
+
const start = lines.findIndex(line => line.trim() === 'description: >-')
|
|
472
|
+
if (start === -1) {
|
|
473
|
+
return null
|
|
474
|
+
}
|
|
475
|
+
const descLines = []
|
|
476
|
+
for (const line of lines.slice(start + 1)) {
|
|
477
|
+
if (!LEADING_SPACES_RE.test(line)) {
|
|
478
|
+
break
|
|
479
|
+
}
|
|
480
|
+
descLines.push(line.replace(LEADING_SPACES_RE, '').trimEnd())
|
|
481
|
+
}
|
|
482
|
+
if (descLines.length === 0) {
|
|
483
|
+
return null
|
|
484
|
+
}
|
|
485
|
+
return descLines.join(' ').trim()
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Підготовка опису skill для вставки в звичайний markdown (заголовок H1, bullet без code fence).
|
|
490
|
+
* Послідовність `<id>` сприймається markdownlint (MD033) як inline HTML — замінюємо на `{id}`.
|
|
491
|
+
* @param {string} desc один рядок з YAML frontmatter SKILL.md
|
|
492
|
+
* @returns {string} той самий рядок після заміни літералу з кутовими дужками навколо id на плейсхолдер у фігурних дужках (MD033).
|
|
493
|
+
*/
|
|
494
|
+
function skillDescriptionSafeForMarkdownInline(desc) {
|
|
495
|
+
return desc.replaceAll('<id>', '{id}')
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* YAML frontmatter для `.claude/commands/*.md`: поле `description` потрібне розширенню VSCode,
|
|
500
|
+
* щоб команди з’являлись у списку. Текст збігається з полем `description` у frontmatter `SKILL.md`.
|
|
501
|
+
* @param {string} descriptionRaw значення з `extractSkillDescription` (може бути порожнім)
|
|
502
|
+
* @returns {string} блок `---` … `---` і порожній рядок після
|
|
503
|
+
*/
|
|
504
|
+
function formatClaudeCommandFrontmatter(descriptionRaw) {
|
|
505
|
+
let text = skillDescriptionSafeForMarkdownInline(String(descriptionRaw || '').trim())
|
|
506
|
+
if (!text) {
|
|
507
|
+
text = 'Див. SKILL.md у каталозі скілу в .cursor/skills.'
|
|
508
|
+
}
|
|
509
|
+
return `---\ndescription: >-\n ${text}\n---\n\n`
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* YAML frontmatter для `.pi/skills/<dir>/SKILL.md` згідно зі специфікацією pi.dev:
|
|
514
|
+
* обов'язкові поля `name` (1-64, `[a-z0-9-]`) і `description` (≤ 1024). Текст description збігається
|
|
515
|
+
* з полем `description` у frontmatter джерельного `SKILL.md`.
|
|
516
|
+
* @param {string} skillName ім'я скілу (наприклад `n-fix`); має бути валідним pi-name
|
|
517
|
+
* @param {string} descriptionRaw значення з `extractSkillDescription` (може бути порожнім)
|
|
518
|
+
* @returns {string} блок `---` … `---` і порожній рядок після
|
|
519
|
+
*/
|
|
520
|
+
function formatPiSkillFrontmatter(skillName, descriptionRaw) {
|
|
521
|
+
let text = skillDescriptionSafeForMarkdownInline(String(descriptionRaw || '').trim())
|
|
522
|
+
if (!text) {
|
|
523
|
+
text = 'Див. SKILL.md у каталозі скілу в .cursor/skills.'
|
|
524
|
+
}
|
|
525
|
+
return `---\nname: ${skillName}\ndescription: >-\n ${text}\n---\n\n`
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Базові імена файлів .mdc, які очікуються згідно з .n-rules.json (префікс n-).
|
|
530
|
+
* @param {string[]} configRules елементи масиву rules з конфігу
|
|
531
|
+
* @returns {Set<string>} множина очікуваних імен файлів (наприклад n-bun.mdc)
|
|
532
|
+
*/
|
|
533
|
+
function expectedManagedRuleBasenames(configRules) {
|
|
534
|
+
return new Set(configRules.map(rule => `${RULE_PREFIX}${normalizeRuleName(rule)}.mdc`))
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Видаляє з каталогу правил файли *.mdc з префіксом n-, яких немає у конфігурації.
|
|
539
|
+
* Файли без префікса n- не змінює.
|
|
540
|
+
* @param {string} rulesDir абсолютний шлях до .cursor/rules
|
|
541
|
+
* @param {string[]} configRules елементи масиву rules з .n-rules.json
|
|
542
|
+
* @returns {Promise<string[]>} відсортовані імена видалених файлів
|
|
543
|
+
*/
|
|
544
|
+
async function removeOrphanManagedRuleFiles(rulesDir, configRules) {
|
|
545
|
+
if (!existsSync(rulesDir)) {
|
|
546
|
+
return []
|
|
547
|
+
}
|
|
548
|
+
const expected = expectedManagedRuleBasenames(configRules)
|
|
549
|
+
const names = await readdir(rulesDir)
|
|
550
|
+
const removed = []
|
|
551
|
+
for (const name of names) {
|
|
552
|
+
if (!(name.endsWith('.mdc') && name.startsWith(RULE_PREFIX) && !expected.has(name))) {
|
|
553
|
+
continue
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
await unlink(join(rulesDir, name))
|
|
557
|
+
removed.push(name)
|
|
558
|
+
}
|
|
559
|
+
return removed.toSorted((a, b) => a.localeCompare(b))
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Повертає відсортований список директорій skills у `.cursor/skills`.
|
|
564
|
+
* Директорія вважається skill-каталогом, якщо це підкаталог (без префікса `.`).
|
|
565
|
+
* @returns {Promise<string[]>} імена директорій (наприклад `n-fix`, `custom-skill`)
|
|
566
|
+
*/
|
|
567
|
+
async function listProjectSkillDirNames() {
|
|
568
|
+
const skillsRoot = join(cwd(), SKILLS_DIR)
|
|
569
|
+
if (!existsSync(skillsRoot)) {
|
|
570
|
+
return []
|
|
571
|
+
}
|
|
572
|
+
const entries = await readdir(skillsRoot, { withFileTypes: true })
|
|
573
|
+
return entries
|
|
574
|
+
.filter(entry => entry.isDirectory() && !entry.name.startsWith('.'))
|
|
575
|
+
.map(entry => entry.name)
|
|
576
|
+
.toSorted((a, b) => a.localeCompare(b))
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Формує markdown-рядки для секції Skills у AGENTS.md з усіх skill-директорій на диску.
|
|
581
|
+
* @returns {Promise<{ name: string }[]>} елементи з полем name для Mustache-секції skills
|
|
582
|
+
*/
|
|
583
|
+
async function buildSkillBulletItems() {
|
|
584
|
+
const skillsRoot = join(cwd(), SKILLS_DIR)
|
|
585
|
+
const skillDirNames = await listProjectSkillDirNames()
|
|
586
|
+
const items = []
|
|
587
|
+
for (const dirName of skillDirNames) {
|
|
588
|
+
const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
|
|
589
|
+
let desc = ''
|
|
590
|
+
if (existsSync(skillMdPath)) {
|
|
591
|
+
const text = await readFile(skillMdPath, 'utf8')
|
|
592
|
+
const parsed = extractSkillDescription(text)
|
|
593
|
+
if (parsed) {
|
|
594
|
+
desc = skillDescriptionSafeForMarkdownInline(parsed)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const pathLine = `- \`${SKILLS_DIR}/${dirName}/SKILL.md\``
|
|
598
|
+
const line = desc ? `${pathLine} — ${desc}` : pathLine
|
|
599
|
+
items.push({ name: line })
|
|
600
|
+
}
|
|
601
|
+
return items
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Видаляє каталоги n-* у .cursor/skills, яких немає у конфігурації skills
|
|
606
|
+
* @param {string} skillsRoot абсолютний шлях до .cursor/skills
|
|
607
|
+
* @param {string[]} configSkills елементи масиву skills з .n-rules.json
|
|
608
|
+
* @returns {Promise<string[]>} імена видалених каталогів
|
|
609
|
+
*/
|
|
610
|
+
async function removeOrphanManagedSkillDirs(skillsRoot, configSkills) {
|
|
611
|
+
if (!existsSync(skillsRoot)) {
|
|
612
|
+
return []
|
|
613
|
+
}
|
|
614
|
+
const expected = new Set(configSkills.map(s => managedSkillDirName(s)))
|
|
615
|
+
const entries = await readdir(skillsRoot, { withFileTypes: true })
|
|
616
|
+
const removed = []
|
|
617
|
+
for (const e of entries) {
|
|
618
|
+
const isManagedDir = e.isDirectory() && e.name.startsWith(RULE_PREFIX)
|
|
619
|
+
const isOrphan = isManagedDir && !expected.has(e.name)
|
|
620
|
+
if (isOrphan) {
|
|
621
|
+
await rm(join(skillsRoot, e.name), { recursive: true, force: true })
|
|
622
|
+
removed.push(e.name)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return removed.toSorted((a, b) => a.localeCompare(b))
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Рендерить коротку секцію для CLAUDE.md: full-прогони lint мають вбудовану
|
|
630
|
+
* глобальну чергу з видимим прогресом, дельта-запуски йдуть паралельно без лока.
|
|
631
|
+
* @returns {string[]} рядки для вставки (з порожнім рядком на початку)
|
|
632
|
+
*/
|
|
633
|
+
function buildClaudeLintParallelismSectionLines() {
|
|
634
|
+
return [
|
|
635
|
+
'',
|
|
636
|
+
'## Лінт і ESLint (паралелізм)',
|
|
637
|
+
'',
|
|
638
|
+
'Дельта-`lint` (типовий задачний прогін) — **без черги**, паралельні запуски по різних файлах дозволені. `npx @7n/rules lint --full` має **вбудовану глобальну чергу** (spec 2026-07-03): один full-прогін на машину; запуск у черзі показує свою позицію, решту черги і живий прогрес-бар активного прогону (`⏳ lint --full у черзі #… · працює pid … [██…] 5/12 · …` — штатна черга, не зависання; fail-closed таймаут 45 хв). Ідентичний повтор --full на незміненому дереві дедуплікується (`♻️ … пропускаю`). Координувати запуски вручну не треба. Деталі: `.cursor/skills/n-lint/SKILL.md`.',
|
|
639
|
+
''
|
|
640
|
+
]
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Рендерить секцію для CLAUDE.md: скіли з `main.json.worktree === true` запускаються
|
|
645
|
+
* лише в окремому git-worktree (дублює fail-fast банер `SKILL.md` як завжди-читане правило).
|
|
646
|
+
* @returns {string[]} рядки для вставки (з порожнім рядком на початку)
|
|
647
|
+
*/
|
|
648
|
+
function buildClaudeWorktreeEnforcementSectionLines() {
|
|
649
|
+
return [
|
|
650
|
+
'',
|
|
651
|
+
'## Worktree-only skills (`main.json` → `worktree: true`)',
|
|
652
|
+
'',
|
|
653
|
+
'Скіл із **`worktree: true`** у `main.json` запускається **виключно** в окремому git-worktree (`.worktrees/<current-branch>-<suffix>/`) — **не** в основному дереві й **не** паралельно. Перший крок такого скіла (блок `n-rules:worktree:start` у його `SKILL.md`) — **preflight**: якщо `git rev-parse --show-toplevel` не вказує під `.worktrees/`, **STOP** і не питай користувача про назву гілки; створи worktree від поточної гілки готовим snippet з `SKILL.md` за конвенцією `<current-branch>-<suffix>` і без shell expansion (без command substitution, variable expansion чи backticks). Чисте робоче дерево — **не** привід пропустити preflight.',
|
|
654
|
+
''
|
|
655
|
+
]
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Рендерить секцію для CLAUDE.md: doc-files — обовʼязковий крок задачі (як lint).
|
|
660
|
+
* Після зміни кодового файлу його дока має бути перегенерована; застарілість
|
|
661
|
+
* детермінується за CRC і контролюється Stop-hook'ом.
|
|
662
|
+
* @returns {string[]} рядки для вставки (з порожнім рядком на початку)
|
|
663
|
+
*/
|
|
664
|
+
function buildClaudeDocFilesSectionLines() {
|
|
665
|
+
return [
|
|
666
|
+
'',
|
|
667
|
+
'## Файлова документація (`doc-files` — обовʼязковий крок, як lint)',
|
|
668
|
+
'',
|
|
669
|
+
'Після зміни чи додавання кодового файлу його файлова дока (`<dir>/docs/<stem>.md`) має бути **актуальною** — це **обовʼязковий крок кожної задачі**, нарівні з lint. Застарілість детермінується за **CRC** джерела у frontmatter доки. PostToolUse hook (`hook --post-tool-use`) **сигналить** про дрейф після правки через per-file lint правила. Регенерація — `/doc-files` (JS-оркестрована, не диспатч субагентів). Агрегуюча дока (module-summary, доменні) — окремий скіл `/doc-aggregate`, за запитом.',
|
|
670
|
+
''
|
|
671
|
+
]
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Рендерить секцію Skills для CLAUDE.md з урахуванням наявних slash-команд.
|
|
676
|
+
* @returns {Promise<string[]>} готові рядки секції (або порожній масив)
|
|
677
|
+
*/
|
|
678
|
+
async function buildClaudeSkillsSectionLines() {
|
|
679
|
+
const skillDirNames = await listProjectSkillDirNames()
|
|
680
|
+
if (skillDirNames.length === 0) {
|
|
681
|
+
return []
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const lines = ['', '## Skills', '']
|
|
685
|
+
const skillsRoot = join(cwd(), SKILLS_DIR)
|
|
686
|
+
const commandsRoot = join(cwd(), COMMANDS_DIR)
|
|
687
|
+
for (const dirName of skillDirNames) {
|
|
688
|
+
const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
|
|
689
|
+
const commandPath = join(commandsRoot, `${dirName}.md`)
|
|
690
|
+
let desc = ''
|
|
691
|
+
if (existsSync(skillMdPath)) {
|
|
692
|
+
const text = await readFile(skillMdPath, 'utf8')
|
|
693
|
+
const parsed = extractSkillDescription(text)
|
|
694
|
+
if (parsed) {
|
|
695
|
+
desc = skillDescriptionSafeForMarkdownInline(parsed)
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const ref = `- \`${SKILLS_DIR}/${dirName}/SKILL.md\``
|
|
699
|
+
lines.push(desc ? `${ref} — ${desc}` : ref)
|
|
700
|
+
if (existsSync(commandPath)) {
|
|
701
|
+
lines.push(` Команда: \`/${dirName}\``)
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return lines
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Генерує CLAUDE.md у корені cwd з at-імпортами всіх .mdc-правил та посиланнями на skills.
|
|
709
|
+
* Завдяки цьому Claude Code автоматично завантажує вміст кожного правила при старті.
|
|
710
|
+
* @returns {Promise<void>}
|
|
711
|
+
*/
|
|
712
|
+
/**
|
|
713
|
+
* @param {string[]} [ignore] директорії заборонені для редагування
|
|
714
|
+
*/
|
|
715
|
+
async function syncClaudeMd(ignore) {
|
|
716
|
+
const lines = [`<!-- Цей файл генерується автоматично через \`npx ${PACKAGE_NAME}\`. Не редагуй вручну. -->`, '']
|
|
717
|
+
|
|
718
|
+
if (Array.isArray(ignore) && ignore.length > 0) {
|
|
719
|
+
lines.push('## Захищені директорії', '', 'Ніколи не змінюй, не видаляй і не створюй файли у цих директоріях:')
|
|
720
|
+
for (const dir of ignore) {
|
|
721
|
+
let d = dir
|
|
722
|
+
while (d.endsWith('/')) d = d.slice(0, -1)
|
|
723
|
+
lines.push(`- \`${d}/\``)
|
|
724
|
+
}
|
|
725
|
+
lines.push('')
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const mdcFiles = await listProjectRulesMdcFiles()
|
|
729
|
+
for (const mdcFile of mdcFiles) {
|
|
730
|
+
lines.push(`@${RULES_DIR}/${mdcFile}`)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
lines.push(
|
|
734
|
+
...buildClaudeLintParallelismSectionLines(),
|
|
735
|
+
...buildClaudeWorktreeEnforcementSectionLines(),
|
|
736
|
+
...buildClaudeDocFilesSectionLines()
|
|
737
|
+
)
|
|
738
|
+
|
|
739
|
+
const skillsSectionLines = await buildClaudeSkillsSectionLines()
|
|
740
|
+
lines.push(...skillsSectionLines)
|
|
741
|
+
const claudeMdPath = join(cwd(), 'CLAUDE.md')
|
|
742
|
+
const hadFile = existsSync(claudeMdPath)
|
|
743
|
+
await writeFile(claudeMdPath, formatGeneratedMarkdownLines(lines), 'utf8')
|
|
744
|
+
console.log(hadFile ? `📝 Оновлено CLAUDE.md` : `📝 Створено CLAUDE.md`)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Повністю перезаписує AGENTS.md у корені cwd з npm/AGENTS.template.md
|
|
749
|
+
* @param {string} [agentsTemplatePath] шлях до AGENTS.template.md у корені пакету-джерела
|
|
750
|
+
* @returns {Promise<void>} завершення запису файлу
|
|
751
|
+
*/
|
|
752
|
+
async function syncAgentsMd(agentsTemplatePath = BUNDLED_AGENTS_TEMPLATE_PATH) {
|
|
753
|
+
if (!existsSync(agentsTemplatePath)) {
|
|
754
|
+
throw new Error(
|
|
755
|
+
`Не знайдено шаблон ${AGENTS_TEMPLATE_FILE} у пакеті.\n` +
|
|
756
|
+
`Очікуваний шлях: ${agentsTemplatePath}\n` +
|
|
757
|
+
`Перевстановіть ${PACKAGE_NAME}.`
|
|
758
|
+
)
|
|
759
|
+
}
|
|
760
|
+
const templateText = await readFile(agentsTemplatePath, 'utf8')
|
|
761
|
+
const mdcFiles = await listProjectRulesMdcFiles()
|
|
762
|
+
const skillItems = await buildSkillBulletItems()
|
|
763
|
+
const commandItems = await buildAgentsCommandBulletItems(cwd())
|
|
764
|
+
const body = renderAgentsTemplate(templateText, mdcFiles, skillItems, commandItems)
|
|
765
|
+
const agentsPath = join(cwd(), AGENTS_FILE)
|
|
766
|
+
const hadFile = existsSync(agentsPath)
|
|
767
|
+
await writeFile(agentsPath, body.endsWith('\n') ? body : `${body}\n`, 'utf8')
|
|
768
|
+
console.log(
|
|
769
|
+
hadFile
|
|
770
|
+
? `📝 Оновлено ${AGENTS_FILE} з ${AGENTS_TEMPLATE_FILE}`
|
|
771
|
+
: `📝 Створено ${AGENTS_FILE} з ${AGENTS_TEMPLATE_FILE}`
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Копіює лише skills зі списку configSkills (джерело: skills/<id>/ у пакеті)
|
|
777
|
+
* @param {string[]} configSkills id без префікса n-
|
|
778
|
+
* @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
|
|
779
|
+
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих копіювань
|
|
780
|
+
*/
|
|
781
|
+
async function syncSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
|
|
782
|
+
if (configSkills.length === 0 || !existsSync(bundledSkillsDir)) {
|
|
783
|
+
return { success: 0, fail: 0 }
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const skillsRoot = join(cwd(), SKILLS_DIR)
|
|
787
|
+
await mkdir(skillsRoot, { recursive: true })
|
|
788
|
+
|
|
789
|
+
let success = 0
|
|
790
|
+
let fail = 0
|
|
791
|
+
|
|
792
|
+
for (const skillId of configSkills) {
|
|
793
|
+
const id = normalizeSkillId(skillId)
|
|
794
|
+
const srcDir = join(bundledSkillsDir, id)
|
|
795
|
+
const destDirName = managedSkillDirName(skillId)
|
|
796
|
+
const destDir = join(skillsRoot, destDirName)
|
|
797
|
+
|
|
798
|
+
process.stdout.write(` ⬇ ${id} → ${SKILLS_DIR}/${destDirName} ... `)
|
|
799
|
+
if (existsSync(srcDir)) {
|
|
800
|
+
try {
|
|
801
|
+
await mkdir(destDir, { recursive: true })
|
|
802
|
+
const meta = readSkillMetaRaw(srcDir)
|
|
803
|
+
const worktree = meta?.worktree === true
|
|
804
|
+
// root-guard для in-place скілів (мутують CWD без worktree-ізоляції).
|
|
805
|
+
// Worktree-скіли root-assert уже мають у worktree-блоці, тож тут лише !worktree.
|
|
806
|
+
const rootOnly = !worktree && meta?.requireRoot === true
|
|
807
|
+
const entries = await readdir(srcDir, { withFileTypes: true })
|
|
808
|
+
for (const entry of entries) {
|
|
809
|
+
// Лише top-level файли скіла. `main.json` — метадані (не для споживача);
|
|
810
|
+
// підкаталоги (`js/` — скіл-специфічний код) виконуються з пакета через
|
|
811
|
+
// `npx`, у проєкт не копіюються (як `npm/rules/<id>/js/`). Див. scripts.mdc.
|
|
812
|
+
if (!entry.isFile() || entry.name === 'main.json') continue
|
|
813
|
+
let content = await readFile(join(srcDir, entry.name), 'utf8')
|
|
814
|
+
if (entry.name === 'SKILL.md') {
|
|
815
|
+
content = injectWorktreeNotice(content, worktree)
|
|
816
|
+
content = injectRootNotice(content, rootOnly)
|
|
817
|
+
}
|
|
818
|
+
await writeFile(join(destDir, entry.name), content, 'utf8')
|
|
819
|
+
}
|
|
820
|
+
console.log(`✅`)
|
|
821
|
+
success++
|
|
822
|
+
} catch (error) {
|
|
823
|
+
console.log(`❌`)
|
|
824
|
+
console.error(` Помилка: ${error.message}`)
|
|
825
|
+
fail++
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
console.log(`❌`)
|
|
829
|
+
console.error(` Немає каталогу в пакеті: skills/${id}`)
|
|
830
|
+
fail++
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return { success, fail }
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Синхронізує .claude/commands/n-<id>.md зі skills пакету.
|
|
838
|
+
* У кожному файлі обов’язково YAML frontmatter з `description` (як у `SKILL.md`), інакше команди
|
|
839
|
+
* не з’являються у розширенні VSCode; далі — заголовок H1 лише з імені команди (без повтору опису) і посилання на `.cursor/skills/…/SKILL.md`.
|
|
840
|
+
* @param {string[]} configSkills id без префікса n-
|
|
841
|
+
* @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
|
|
842
|
+
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
|
|
843
|
+
*/
|
|
844
|
+
async function syncCommands(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
|
|
845
|
+
if (configSkills.length === 0 || !existsSync(bundledSkillsDir)) {
|
|
846
|
+
return { success: 0, fail: 0 }
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const commandsDir = join(cwd(), COMMANDS_DIR)
|
|
850
|
+
await mkdir(commandsDir, { recursive: true })
|
|
851
|
+
|
|
852
|
+
let success = 0
|
|
853
|
+
let fail = 0
|
|
854
|
+
|
|
855
|
+
for (const skillId of configSkills) {
|
|
856
|
+
const id = normalizeSkillId(skillId)
|
|
857
|
+
const srcSkillMd = join(bundledSkillsDir, id, 'SKILL.md')
|
|
858
|
+
const destDirName = managedSkillDirName(skillId)
|
|
859
|
+
const destFile = join(commandsDir, `${RULE_PREFIX}${id}.md`)
|
|
860
|
+
|
|
861
|
+
process.stdout.write(` ⬇ ${id} → ${COMMANDS_DIR}/${RULE_PREFIX}${id}.md ... `)
|
|
862
|
+
if (existsSync(srcSkillMd)) {
|
|
863
|
+
try {
|
|
864
|
+
const raw = await readFile(srcSkillMd, 'utf8')
|
|
865
|
+
const descRaw = extractSkillDescription(raw)
|
|
866
|
+
const frontmatter = formatClaudeCommandFrontmatter(descRaw || '')
|
|
867
|
+
const header = `# ${RULE_PREFIX}${id}\n\n`
|
|
868
|
+
const body = `${frontmatter}${header}Виконай інструкції зі скілу \`.cursor/skills/${destDirName}/SKILL.md\`.\n`
|
|
869
|
+
await writeFile(destFile, body, 'utf8')
|
|
870
|
+
console.log(`✅`)
|
|
871
|
+
success++
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.log(`❌`)
|
|
874
|
+
console.error(` Помилка: ${error.message}`)
|
|
875
|
+
fail++
|
|
876
|
+
}
|
|
877
|
+
} else {
|
|
878
|
+
console.log(`❌`)
|
|
879
|
+
console.error(` Немає SKILL.md у пакеті: skills/${id}`)
|
|
880
|
+
fail++
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return { success, fail }
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Видаляє файли n-*.md у .claude/commands, яких немає у конфігурації skills
|
|
888
|
+
* @param {string} commandsDir абсолютний шлях до .claude/commands
|
|
889
|
+
* @param {string[]} configSkills id без префікса n-
|
|
890
|
+
* @returns {Promise<string[]>} імена видалених файлів
|
|
891
|
+
*/
|
|
892
|
+
async function removeOrphanManagedCommandFiles(commandsDir, configSkills) {
|
|
893
|
+
if (!existsSync(commandsDir)) {
|
|
894
|
+
return []
|
|
895
|
+
}
|
|
896
|
+
const expected = new Set(configSkills.map(s => `${RULE_PREFIX}${normalizeSkillId(s)}.md`))
|
|
897
|
+
const names = await readdir(commandsDir)
|
|
898
|
+
const removed = []
|
|
899
|
+
for (const name of names) {
|
|
900
|
+
if (!(name.endsWith('.md') && name.startsWith(RULE_PREFIX) && !expected.has(name))) {
|
|
901
|
+
continue
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
await unlink(join(commandsDir, name))
|
|
905
|
+
removed.push(name)
|
|
906
|
+
}
|
|
907
|
+
return removed.toSorted((a, b) => a.localeCompare(b))
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Синхронізує .claude/commands/{dirName}.md для всіх локальних скілів з .cursor/skills/
|
|
912
|
+
* що не керуються пакетом (відсутні в configSkills). Frontmatter `description` — як у відповідному SKILL.md.
|
|
913
|
+
* @param {string[]} configSkills id керованих skills (вже оброблені syncCommands)
|
|
914
|
+
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
|
|
915
|
+
*/
|
|
916
|
+
async function syncLocalOnlySkillCommands(configSkills) {
|
|
917
|
+
const skillsRoot = join(cwd(), SKILLS_DIR)
|
|
918
|
+
if (!existsSync(skillsRoot)) return { success: 0, fail: 0 }
|
|
919
|
+
|
|
920
|
+
const commandsDir = join(cwd(), COMMANDS_DIR)
|
|
921
|
+
await mkdir(commandsDir, { recursive: true })
|
|
922
|
+
|
|
923
|
+
const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
|
|
924
|
+
const allDirNames = await listProjectSkillDirNames()
|
|
925
|
+
const localOnly = allDirNames.filter(d => !managedDirNames.has(d))
|
|
926
|
+
|
|
927
|
+
let success = 0
|
|
928
|
+
let fail = 0
|
|
929
|
+
|
|
930
|
+
for (const dirName of localOnly) {
|
|
931
|
+
const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
|
|
932
|
+
const destFile = join(commandsDir, `${dirName}.md`)
|
|
933
|
+
|
|
934
|
+
process.stdout.write(` ⬇ ${dirName} → ${COMMANDS_DIR}/${dirName}.md ... `)
|
|
935
|
+
try {
|
|
936
|
+
let descRaw = ''
|
|
937
|
+
if (existsSync(skillMdPath)) {
|
|
938
|
+
const raw = await readFile(skillMdPath, 'utf8')
|
|
939
|
+
const parsed = extractSkillDescription(raw)
|
|
940
|
+
if (parsed) descRaw = parsed
|
|
941
|
+
}
|
|
942
|
+
const frontmatter = formatClaudeCommandFrontmatter(descRaw)
|
|
943
|
+
const header = `# ${dirName}\n\n`
|
|
944
|
+
const body = `${frontmatter}${header}Виконай інструкції зі скілу \`${SKILLS_DIR}/${dirName}/SKILL.md\`.\n`
|
|
945
|
+
await writeFile(destFile, body, 'utf8')
|
|
946
|
+
console.log(`✅`)
|
|
947
|
+
success++
|
|
948
|
+
} catch (error) {
|
|
949
|
+
console.log(`❌`)
|
|
950
|
+
console.error(` Помилка: ${errorMessage(error)}`)
|
|
951
|
+
fail++
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return { success, fail }
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Видаляє .claude/commands/{dirName}.md файли локальних скілів, яких більше немає в .cursor/skills/
|
|
959
|
+
* @param {string} commandsDir абсолютний шлях до .claude/commands
|
|
960
|
+
* @param {string[]} configSkills id керованих skills
|
|
961
|
+
* @returns {Promise<string[]>} імена видалених файлів
|
|
962
|
+
*/
|
|
963
|
+
async function removeOrphanLocalSkillCommandFiles(commandsDir, configSkills) {
|
|
964
|
+
if (!existsSync(commandsDir)) return []
|
|
965
|
+
|
|
966
|
+
const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
|
|
967
|
+
const allDirNames = new Set(await listProjectSkillDirNames())
|
|
968
|
+
const names = await readdir(commandsDir)
|
|
969
|
+
const removed = []
|
|
970
|
+
|
|
971
|
+
for (const name of names) {
|
|
972
|
+
if (!name.endsWith('.md') || name.startsWith(RULE_PREFIX)) continue
|
|
973
|
+
const dirName = name.slice(0, -3)
|
|
974
|
+
if (!managedDirNames.has(dirName) && !allDirNames.has(dirName)) {
|
|
975
|
+
await unlink(join(commandsDir, name))
|
|
976
|
+
removed.push(name)
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return removed.toSorted((a, b) => a.localeCompare(b))
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/**
|
|
983
|
+
* Синхронізує .pi/skills/n-<id>/SKILL.md зі skills пакету для pi.dev-сумісності.
|
|
984
|
+
* Pi-skill — це директорія з SKILL.md (frontmatter `name`+`description`), тіло-делегат на джерельний
|
|
985
|
+
* `.cursor/skills/<dir>/SKILL.md`. Симетрично до `syncCommands`, але дир замість `.md`-файлу.
|
|
986
|
+
* @param {string[]} configSkills id без префікса n-
|
|
987
|
+
* @param {string} [bundledSkillsDir] каталог `skills/` у корені пакету-джерела
|
|
988
|
+
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
|
|
989
|
+
*/
|
|
990
|
+
async function syncPiSkills(configSkills, bundledSkillsDir = BUNDLED_SKILLS_DIR) {
|
|
991
|
+
if (configSkills.length === 0 || !existsSync(bundledSkillsDir)) {
|
|
992
|
+
return { success: 0, fail: 0 }
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const piSkillsRoot = join(cwd(), PI_SKILLS_DIR)
|
|
996
|
+
await mkdir(piSkillsRoot, { recursive: true })
|
|
997
|
+
|
|
998
|
+
let success = 0
|
|
999
|
+
let fail = 0
|
|
1000
|
+
|
|
1001
|
+
for (const skillId of configSkills) {
|
|
1002
|
+
const id = normalizeSkillId(skillId)
|
|
1003
|
+
const srcSkillMd = join(bundledSkillsDir, id, 'SKILL.md')
|
|
1004
|
+
const destDirName = managedSkillDirName(skillId)
|
|
1005
|
+
const destDir = join(piSkillsRoot, destDirName)
|
|
1006
|
+
const destFile = join(destDir, 'SKILL.md')
|
|
1007
|
+
|
|
1008
|
+
process.stdout.write(` ⬇ ${id} → ${PI_SKILLS_DIR}/${destDirName}/SKILL.md ... `)
|
|
1009
|
+
if (existsSync(srcSkillMd)) {
|
|
1010
|
+
try {
|
|
1011
|
+
const raw = await readFile(srcSkillMd, 'utf8')
|
|
1012
|
+
const descRaw = extractSkillDescription(raw)
|
|
1013
|
+
await mkdir(destDir, { recursive: true })
|
|
1014
|
+
const frontmatter = formatPiSkillFrontmatter(destDirName, descRaw || '')
|
|
1015
|
+
const header = `# ${destDirName}\n\n`
|
|
1016
|
+
const body = `${frontmatter}${header}Виконай інструкції зі скілу \`.cursor/skills/${destDirName}/SKILL.md\`.\n`
|
|
1017
|
+
await writeFile(destFile, body, 'utf8')
|
|
1018
|
+
console.log(`✅`)
|
|
1019
|
+
success++
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.log(`❌`)
|
|
1022
|
+
console.error(` Помилка: ${errorMessage(error)}`)
|
|
1023
|
+
fail++
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
console.log(`❌`)
|
|
1027
|
+
console.error(` Немає SKILL.md у пакеті: skills/${id}`)
|
|
1028
|
+
fail++
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return { success, fail }
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* Синхронізує .pi/skills/{dirName}/SKILL.md для всіх локальних скілів з .cursor/skills/
|
|
1036
|
+
* що не керуються пакетом. Симетрично до `syncLocalOnlySkillCommands`.
|
|
1037
|
+
* @param {string[]} configSkills id керованих skills (уже оброблені syncPiSkills)
|
|
1038
|
+
* @returns {Promise<{ success: number, fail: number }>} лічильники успішних і невдалих записів
|
|
1039
|
+
*/
|
|
1040
|
+
async function syncLocalOnlyPiSkills(configSkills) {
|
|
1041
|
+
const skillsRoot = join(cwd(), SKILLS_DIR)
|
|
1042
|
+
if (!existsSync(skillsRoot)) return { success: 0, fail: 0 }
|
|
1043
|
+
|
|
1044
|
+
const piSkillsRoot = join(cwd(), PI_SKILLS_DIR)
|
|
1045
|
+
await mkdir(piSkillsRoot, { recursive: true })
|
|
1046
|
+
|
|
1047
|
+
const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
|
|
1048
|
+
const allDirNames = await listProjectSkillDirNames()
|
|
1049
|
+
const localOnly = allDirNames.filter(d => !managedDirNames.has(d))
|
|
1050
|
+
|
|
1051
|
+
let success = 0
|
|
1052
|
+
let fail = 0
|
|
1053
|
+
|
|
1054
|
+
for (const dirName of localOnly) {
|
|
1055
|
+
const skillMdPath = join(skillsRoot, dirName, 'SKILL.md')
|
|
1056
|
+
const destDir = join(piSkillsRoot, dirName)
|
|
1057
|
+
const destFile = join(destDir, 'SKILL.md')
|
|
1058
|
+
|
|
1059
|
+
process.stdout.write(` ⬇ ${dirName} → ${PI_SKILLS_DIR}/${dirName}/SKILL.md ... `)
|
|
1060
|
+
try {
|
|
1061
|
+
let descRaw = ''
|
|
1062
|
+
if (existsSync(skillMdPath)) {
|
|
1063
|
+
const raw = await readFile(skillMdPath, 'utf8')
|
|
1064
|
+
const parsed = extractSkillDescription(raw)
|
|
1065
|
+
if (parsed) descRaw = parsed
|
|
1066
|
+
}
|
|
1067
|
+
await mkdir(destDir, { recursive: true })
|
|
1068
|
+
const frontmatter = formatPiSkillFrontmatter(dirName, descRaw)
|
|
1069
|
+
const header = `# ${dirName}\n\n`
|
|
1070
|
+
const body = `${frontmatter}${header}Виконай інструкції зі скілу \`${SKILLS_DIR}/${dirName}/SKILL.md\`.\n`
|
|
1071
|
+
await writeFile(destFile, body, 'utf8')
|
|
1072
|
+
console.log(`✅`)
|
|
1073
|
+
success++
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
console.log(`❌`)
|
|
1076
|
+
console.error(` Помилка: ${errorMessage(error)}`)
|
|
1077
|
+
fail++
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return { success, fail }
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Видаляє n-* директорії у .pi/skills, яких немає у конфігурації skills.
|
|
1085
|
+
* @param {string} piSkillsDir абсолютний шлях до .pi/skills
|
|
1086
|
+
* @param {string[]} configSkills id без префікса n-
|
|
1087
|
+
* @returns {Promise<string[]>} імена видалених директорій
|
|
1088
|
+
*/
|
|
1089
|
+
async function removeOrphanManagedPiSkillDirs(piSkillsDir, configSkills) {
|
|
1090
|
+
if (!existsSync(piSkillsDir)) return []
|
|
1091
|
+
const expected = new Set(configSkills.map(s => managedSkillDirName(s)))
|
|
1092
|
+
const entries = await readdir(piSkillsDir, { withFileTypes: true })
|
|
1093
|
+
const removed = []
|
|
1094
|
+
for (const entry of entries) {
|
|
1095
|
+
if (!(entry.isDirectory() && entry.name.startsWith(RULE_PREFIX) && !expected.has(entry.name))) {
|
|
1096
|
+
continue
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
await rm(join(piSkillsDir, entry.name), { recursive: true, force: true })
|
|
1100
|
+
removed.push(entry.name)
|
|
1101
|
+
}
|
|
1102
|
+
return removed.toSorted((a, b) => a.localeCompare(b))
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Видаляє .pi/skills/{dirName} директорії локальних скілів, яких більше немає в .cursor/skills/.
|
|
1107
|
+
* @param {string} piSkillsDir абсолютний шлях до .pi/skills
|
|
1108
|
+
* @param {string[]} configSkills id керованих skills
|
|
1109
|
+
* @returns {Promise<string[]>} імена видалених директорій
|
|
1110
|
+
*/
|
|
1111
|
+
async function removeOrphanLocalPiSkillDirs(piSkillsDir, configSkills) {
|
|
1112
|
+
if (!existsSync(piSkillsDir)) return []
|
|
1113
|
+
const managedDirNames = new Set(configSkills.map(s => managedSkillDirName(s)))
|
|
1114
|
+
const allDirNames = new Set(await listProjectSkillDirNames())
|
|
1115
|
+
const entries = await readdir(piSkillsDir, { withFileTypes: true })
|
|
1116
|
+
const removed = []
|
|
1117
|
+
for (const entry of entries) {
|
|
1118
|
+
if (!entry.isDirectory() || entry.name.startsWith(RULE_PREFIX)) continue
|
|
1119
|
+
if (!managedDirNames.has(entry.name) && !allDirNames.has(entry.name)) {
|
|
1120
|
+
await rm(join(piSkillsDir, entry.name), { recursive: true, force: true })
|
|
1121
|
+
removed.push(entry.name)
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return removed.toSorted((a, b) => a.localeCompare(b))
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/**
|
|
1128
|
+
* Людинозрозумілий текст винятку для логів.
|
|
1129
|
+
* @param {unknown} error виняток із catch
|
|
1130
|
+
* @returns {string} текст повідомлення
|
|
1131
|
+
*/
|
|
1132
|
+
function errorMessage(error) {
|
|
1133
|
+
return error instanceof Error ? error.message : String(error)
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/**
|
|
1137
|
+
* Виконує крок синхронізації з уніфікованим логуванням помилки.
|
|
1138
|
+
* @template T
|
|
1139
|
+
* @param {string} prefix префікс повідомлення про помилку
|
|
1140
|
+
* @param {() => Promise<T>} action операція
|
|
1141
|
+
* @returns {Promise<T>} результат операції
|
|
1142
|
+
*/
|
|
1143
|
+
async function runSyncStep(prefix, action) {
|
|
1144
|
+
try {
|
|
1145
|
+
return await action()
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
console.error(`${prefix}${errorMessage(error)}`)
|
|
1148
|
+
throw error
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Виконує `action`, буферизуючи весь його stdout/console-вивід.
|
|
1154
|
+
*
|
|
1155
|
+
* Мотивація: за успішного прогону sync-блоку рядки `⬇ … ✅` і підсумок
|
|
1156
|
+
* (`🧩 Skills: N скопійовано, 0 з помилками`) не несуть користі й лише
|
|
1157
|
+
* захаращують термінал. Тому буфер скидається в реальний stdout **лише**
|
|
1158
|
+
* коли крок повернув `fail > 0` (або кинув виняток); за `fail === 0` —
|
|
1159
|
+
* відкидається мовчки.
|
|
1160
|
+
* @template T
|
|
1161
|
+
* @param {() => Promise<T>} action крок синку, що повертає обʼєкт із лічильником помилок `fail`
|
|
1162
|
+
* @returns {Promise<T>} результат `action` без змін
|
|
1163
|
+
*/
|
|
1164
|
+
async function captureOutput(action) {
|
|
1165
|
+
const buffer = []
|
|
1166
|
+
const realStdoutWrite = process.stdout.write
|
|
1167
|
+
const realLog = console.log
|
|
1168
|
+
const realError = console.error
|
|
1169
|
+
const flush = () => realStdoutWrite.call(process.stdout, buffer.join(''))
|
|
1170
|
+
process.stdout.write = (...args) => {
|
|
1171
|
+
const [chunk] = args
|
|
1172
|
+
buffer.push(typeof chunk === 'string' ? chunk : chunk.toString())
|
|
1173
|
+
const cb = args.find(arg => typeof arg === 'function')
|
|
1174
|
+
if (cb) cb()
|
|
1175
|
+
return true
|
|
1176
|
+
}
|
|
1177
|
+
console.log = (...args) => {
|
|
1178
|
+
buffer.push(`${args.map(String).join(' ')}\n`)
|
|
1179
|
+
}
|
|
1180
|
+
console.error = (...args) => {
|
|
1181
|
+
buffer.push(`${args.map(String).join(' ')}\n`)
|
|
1182
|
+
}
|
|
1183
|
+
try {
|
|
1184
|
+
const result = await action()
|
|
1185
|
+
if (result?.fail > 0) flush()
|
|
1186
|
+
return result
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
flush()
|
|
1189
|
+
throw error
|
|
1190
|
+
} finally {
|
|
1191
|
+
process.stdout.write = realStdoutWrite
|
|
1192
|
+
console.log = realLog
|
|
1193
|
+
console.error = realError
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
/**
|
|
1198
|
+
* Копіює керовані `.mdc` файли з пакету до `.cursor/rules`.
|
|
1199
|
+
* @param {string[]} rules список rules з конфігу
|
|
1200
|
+
* @param {string} bundledRulesDir каталог `rules` пакету-джерела
|
|
1201
|
+
* @param {string} rulesDir абсолютний шлях до `.cursor/rules`
|
|
1202
|
+
* @returns {Promise<{ successCount: number, failCount: number }>} статистика копіювання
|
|
1203
|
+
*/
|
|
1204
|
+
async function syncManagedRuleFiles(rules, bundledRulesDir, rulesDir) {
|
|
1205
|
+
let successCount = 0
|
|
1206
|
+
let failCount = 0
|
|
1207
|
+
for (const rule of rules) {
|
|
1208
|
+
const fileName = `${RULE_PREFIX}${normalizeRuleName(rule)}.mdc`
|
|
1209
|
+
const destPath = join(rulesDir, fileName)
|
|
1210
|
+
try {
|
|
1211
|
+
process.stdout.write(` ⬇ ${rule} → ${RULES_DIR}/${fileName} ... `)
|
|
1212
|
+
const content = await readBundledRuleContent(rule, bundledRulesDir)
|
|
1213
|
+
await writeFile(destPath, content, 'utf8')
|
|
1214
|
+
console.log(`✅`)
|
|
1215
|
+
successCount++
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
console.log(`❌`)
|
|
1218
|
+
console.error(` Помилка: ${errorMessage(error)}`)
|
|
1219
|
+
failCount++
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return { successCount, failCount }
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Логує видалені керовані правила/skills/commands у єдиному форматі.
|
|
1227
|
+
* @param {string} title назва сутностей
|
|
1228
|
+
* @param {string} basePath базовий шлях для виводу
|
|
1229
|
+
* @param {string[]} names перелік елементів
|
|
1230
|
+
* @returns {void}
|
|
1231
|
+
*/
|
|
1232
|
+
function logRemovedManagedItems(title, basePath, names) {
|
|
1233
|
+
if (names.length === 0) {
|
|
1234
|
+
return
|
|
1235
|
+
}
|
|
1236
|
+
console.log(`\n🧹 Видалено ${title} поза списком ${CONFIG_FILE} (${names.length}):`)
|
|
1237
|
+
for (const name of names) {
|
|
1238
|
+
console.log(` − ${basePath}/${name}`)
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* Читає поле `version` з `package.json` пакету за абсолютним шляхом до його кореня.
|
|
1244
|
+
* @param {string} packageRoot корінь пакету (тека з `package.json`)
|
|
1245
|
+
* @returns {Promise<string | null>} semver рядком або null, якщо файлу/поля немає або JSON некоректний
|
|
1246
|
+
*/
|
|
1247
|
+
async function readBundledVersionAt(packageRoot) {
|
|
1248
|
+
const p = join(packageRoot, 'package.json')
|
|
1249
|
+
if (!existsSync(p)) {
|
|
1250
|
+
return null
|
|
1251
|
+
}
|
|
1252
|
+
try {
|
|
1253
|
+
const pkg = JSON.parse(await readFile(p, 'utf8'))
|
|
1254
|
+
return typeof pkg.version === 'string' ? pkg.version : null
|
|
1255
|
+
} catch {
|
|
1256
|
+
return null
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Якщо `upgradeNRulesToLatestAndBunInstall` встановив у `node_modules/@7n/rules` версію,
|
|
1262
|
+
* відмінну від тієї, з якої стартував поточний процес (наприклад, з npx-кешу), запускає бінар нової
|
|
1263
|
+
* версії через `spawnSync` і завершує поточний процес із успадкованим exit-кодом. Re-exec потрібен,
|
|
1264
|
+
* бо ES-модулі вже завантажені у V8 (RULE_MIGRATIONS, detectAutoRules тощо) і нова логіка
|
|
1265
|
+
* без повної заміни процесу не підхопиться. Захист від нескінченного циклу — env `NITRA_CURSOR_REEXEC=1`.
|
|
1266
|
+
* @param {string} effectivePackageRoot шлях, повернутий `upgradeNRulesToLatestAndBunInstall`
|
|
1267
|
+
* @returns {Promise<void>} повертається лише якщо re-exec не потрібен; інакше кидає `ReexecHandoff`,
|
|
1268
|
+
* який ловить top-level catch і прокидає exit-код у `process.exitCode`
|
|
1269
|
+
*/
|
|
1270
|
+
async function reexecIfPackageVersionChanged(effectivePackageRoot) {
|
|
1271
|
+
if (env.NITRA_CURSOR_REEXEC === '1') {
|
|
1272
|
+
return
|
|
1273
|
+
}
|
|
1274
|
+
if (effectivePackageRoot === BUNDLED_PACKAGE_ROOT) {
|
|
1275
|
+
return
|
|
1276
|
+
}
|
|
1277
|
+
const currentVersion = await readBundledVersionAt(BUNDLED_PACKAGE_ROOT)
|
|
1278
|
+
const installedVersion = await readBundledVersionAt(effectivePackageRoot)
|
|
1279
|
+
if (!currentVersion || !installedVersion || currentVersion === installedVersion) {
|
|
1280
|
+
return
|
|
1281
|
+
}
|
|
1282
|
+
const newBinPath = join(effectivePackageRoot, 'bin', 'n-rules.js')
|
|
1283
|
+
if (!existsSync(newBinPath)) {
|
|
1284
|
+
return
|
|
1285
|
+
}
|
|
1286
|
+
console.log(
|
|
1287
|
+
`🔁 Перезапуск ${PACKAGE_NAME}: процес стартував на ${currentVersion}, ` +
|
|
1288
|
+
`після self-upgrade встановлено ${installedVersion}.\n` +
|
|
1289
|
+
` Re-exec свіжого бінаря, щоб підхопити нову логіку (RULE_MIGRATIONS, auto-detect тощо).\n`
|
|
1290
|
+
)
|
|
1291
|
+
const result = spawnSync(process.execPath, [newBinPath, ...process.argv.slice(2)], {
|
|
1292
|
+
stdio: 'inherit',
|
|
1293
|
+
env: { ...env, NITRA_CURSOR_REEXEC: '1' }
|
|
1294
|
+
})
|
|
1295
|
+
if (result.error) {
|
|
1296
|
+
throw result.error
|
|
1297
|
+
}
|
|
1298
|
+
throw new ReexecHandoff(typeof result.status === 'number' ? result.status : 1)
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Сентинельна помилка, яку кидає `reexecIfPackageVersionChanged` після успішного re-exec.
|
|
1303
|
+
* Top-level catch розпізнає її й виставляє `process.exitCode = code` без stack-trace —
|
|
1304
|
+
* процес тоді коректно завершується з тим самим кодом, що й child re-exec-у.
|
|
1305
|
+
*/
|
|
1306
|
+
class ReexecHandoff extends Error {
|
|
1307
|
+
/**
|
|
1308
|
+
* @param {number} code exit-код, який повернув child-процес
|
|
1309
|
+
*/
|
|
1310
|
+
constructor(code) {
|
|
1311
|
+
super('reexec-handoff')
|
|
1312
|
+
this.name = 'ReexecHandoff'
|
|
1313
|
+
this.code = code
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Копіює правила з каталогу `mdc/` установленого пакету та синхронізує `.cursor/rules`
|
|
1319
|
+
* @returns {Promise<void>}
|
|
1320
|
+
*/
|
|
1321
|
+
async function runSync() {
|
|
1322
|
+
console.log(`\n🔧 ${PACKAGE_NAME} — завантаження cursor-правил\n`)
|
|
1323
|
+
|
|
1324
|
+
const projectRoot = cwd()
|
|
1325
|
+
const effectivePackageRoot = await runSyncStep(`❌ Не вдалося оновити ${PACKAGE_NAME} або виконати bun i: `, () =>
|
|
1326
|
+
upgradeNRulesToLatestAndBunInstall(projectRoot, BUNDLED_PACKAGE_ROOT)
|
|
1327
|
+
)
|
|
1328
|
+
|
|
1329
|
+
await reexecIfPackageVersionChanged(effectivePackageRoot)
|
|
1330
|
+
|
|
1331
|
+
const bundledRulesDir = join(effectivePackageRoot, 'rules')
|
|
1332
|
+
const bundledSkillsDir = join(effectivePackageRoot, 'skills')
|
|
1333
|
+
const bundledAgentsTemplatePath = join(effectivePackageRoot, AGENTS_TEMPLATE_FILE)
|
|
1334
|
+
|
|
1335
|
+
const config = await runSyncStep('❌ ', () => readConfig({ bundledRulesDir, bundledSkillsDir }))
|
|
1336
|
+
|
|
1337
|
+
const { rules, skills, version, ignore } = config
|
|
1338
|
+
const claudeConfigEnabled = config['claude-config'] !== false
|
|
1339
|
+
const bundledVer = await readBundledVersionAt(effectivePackageRoot)
|
|
1340
|
+
if (bundledVer) {
|
|
1341
|
+
const line =
|
|
1342
|
+
effectivePackageRoot === BUNDLED_PACKAGE_ROOT
|
|
1343
|
+
? `📦 Джерело правил: ${PACKAGE_NAME}@${bundledVer}`
|
|
1344
|
+
: `📦 Джерело правил: ${PACKAGE_NAME}@${bundledVer} (шлях: ${effectivePackageRoot})`
|
|
1345
|
+
console.log(`${line}\n`)
|
|
1346
|
+
}
|
|
1347
|
+
if (version) {
|
|
1348
|
+
console.log(`⚠️ Поле "version" у ${CONFIG_FILE} ігнорується; правила беруться з установленого пакету.\n`)
|
|
1349
|
+
}
|
|
1350
|
+
console.log(`📋 Правил до завантаження: ${rules.length}`)
|
|
1351
|
+
console.log(`📋 Skills до синхронізації: ${skills.length}`)
|
|
1352
|
+
|
|
1353
|
+
await runSyncStep('❌ Не вдалося записати setup-bun-deps action: ', () =>
|
|
1354
|
+
captureOutput(async () => {
|
|
1355
|
+
const { destPath } = await syncSetupBunDepsAction(cwd(), effectivePackageRoot)
|
|
1356
|
+
console.log(`📝 Оновлено ${destPath} (composite setup-bun-deps з пакету)\n`)
|
|
1357
|
+
})
|
|
1358
|
+
)
|
|
1359
|
+
|
|
1360
|
+
const rulesDir = join(cwd(), RULES_DIR)
|
|
1361
|
+
await mkdir(rulesDir, { recursive: true })
|
|
1362
|
+
const { successCount, failCount } = await captureOutput(async () => {
|
|
1363
|
+
const stats = await syncManagedRuleFiles(rules, bundledRulesDir, rulesDir)
|
|
1364
|
+
return { ...stats, fail: stats.failCount }
|
|
1365
|
+
})
|
|
1366
|
+
|
|
1367
|
+
await runSyncStep(`❌ Не вдалося прибрати зайві файли в ${RULES_DIR}: `, async () => {
|
|
1368
|
+
const removed = await removeOrphanManagedRuleFiles(rulesDir, rules)
|
|
1369
|
+
logRemovedManagedItems('правила', RULES_DIR, removed)
|
|
1370
|
+
})
|
|
1371
|
+
|
|
1372
|
+
await runSyncStep('❌ Skills: ', async () => {
|
|
1373
|
+
const { fail: skillFail } = await captureOutput(async () => {
|
|
1374
|
+
const { success: skillOk, fail } = await syncSkills(skills, bundledSkillsDir)
|
|
1375
|
+
if (skills.length > 0) {
|
|
1376
|
+
console.log(`\n🧩 Skills: ${skillOk} скопійовано, ${fail} з помилками`)
|
|
1377
|
+
}
|
|
1378
|
+
return { fail }
|
|
1379
|
+
})
|
|
1380
|
+
const removedSkills = await removeOrphanManagedSkillDirs(join(cwd(), SKILLS_DIR), skills)
|
|
1381
|
+
logRemovedManagedItems('skills', SKILLS_DIR, removedSkills)
|
|
1382
|
+
if (skillFail > 0) {
|
|
1383
|
+
throw new Error(`Не вдалося скопіювати ${skillFail} з ${skills.length} skills`)
|
|
1384
|
+
}
|
|
1385
|
+
})
|
|
1386
|
+
|
|
1387
|
+
await runSyncStep('❌ Commands: ', async () => {
|
|
1388
|
+
const { fail: totalFail } = await captureOutput(async () => {
|
|
1389
|
+
const { success: cmdOk, fail: cmdFail } = await syncCommands(skills, bundledSkillsDir)
|
|
1390
|
+
const { success: localOk, fail: localFail } = await syncLocalOnlySkillCommands(skills)
|
|
1391
|
+
const totalOk = cmdOk + localOk
|
|
1392
|
+
const fail = cmdFail + localFail
|
|
1393
|
+
if (totalOk + fail > 0) {
|
|
1394
|
+
console.log(`\n⌨️ Commands: ${totalOk} скопійовано, ${fail} з помилками`)
|
|
1395
|
+
}
|
|
1396
|
+
return { fail }
|
|
1397
|
+
})
|
|
1398
|
+
const commandsDir = join(cwd(), COMMANDS_DIR)
|
|
1399
|
+
const removedCmds = await removeOrphanManagedCommandFiles(commandsDir, skills)
|
|
1400
|
+
logRemovedManagedItems('commands', COMMANDS_DIR, removedCmds)
|
|
1401
|
+
const removedLocalCmds = await removeOrphanLocalSkillCommandFiles(commandsDir, skills)
|
|
1402
|
+
logRemovedManagedItems('commands (local)', COMMANDS_DIR, removedLocalCmds)
|
|
1403
|
+
if (totalFail > 0) {
|
|
1404
|
+
throw new Error(`Не вдалося скопіювати ${totalFail} commands`)
|
|
1405
|
+
}
|
|
1406
|
+
})
|
|
1407
|
+
|
|
1408
|
+
await runSyncStep('❌ Pi skills: ', async () => {
|
|
1409
|
+
const { fail: totalFail } = await captureOutput(async () => {
|
|
1410
|
+
const { success: piOk, fail: piFail } = await syncPiSkills(skills, bundledSkillsDir)
|
|
1411
|
+
const { success: piLocalOk, fail: piLocalFail } = await syncLocalOnlyPiSkills(skills)
|
|
1412
|
+
const totalOk = piOk + piLocalOk
|
|
1413
|
+
const fail = piFail + piLocalFail
|
|
1414
|
+
if (totalOk + fail > 0) {
|
|
1415
|
+
console.log(`\n🥧 Pi skills: ${totalOk} скопійовано, ${fail} з помилками`)
|
|
1416
|
+
}
|
|
1417
|
+
return { fail }
|
|
1418
|
+
})
|
|
1419
|
+
const piSkillsDir = join(cwd(), PI_SKILLS_DIR)
|
|
1420
|
+
const removedPi = await removeOrphanManagedPiSkillDirs(piSkillsDir, skills)
|
|
1421
|
+
logRemovedManagedItems('pi skills', PI_SKILLS_DIR, removedPi)
|
|
1422
|
+
const removedLocalPi = await removeOrphanLocalPiSkillDirs(piSkillsDir, skills)
|
|
1423
|
+
logRemovedManagedItems('pi skills (local)', PI_SKILLS_DIR, removedLocalPi)
|
|
1424
|
+
if (totalFail > 0) {
|
|
1425
|
+
throw new Error(`Не вдалося скопіювати ${totalFail} pi skills`)
|
|
1426
|
+
}
|
|
1427
|
+
})
|
|
1428
|
+
|
|
1429
|
+
await runSyncStep(`❌ Не вдалося оновити ${AGENTS_FILE}: `, () =>
|
|
1430
|
+
captureOutput(() => syncAgentsMd(bundledAgentsTemplatePath))
|
|
1431
|
+
)
|
|
1432
|
+
await runSyncStep('❌ Не вдалося оновити CLAUDE.md: ', () =>
|
|
1433
|
+
captureOutput(() => syncClaudeMd(/** @type {string[] | undefined} */ (ignore)))
|
|
1434
|
+
)
|
|
1435
|
+
|
|
1436
|
+
await runSyncStep('❌ Не вдалося синхронізувати Claude-конфіг: ', () =>
|
|
1437
|
+
captureOutput(async () => {
|
|
1438
|
+
const result = await syncClaudeConfig({
|
|
1439
|
+
projectRoot: cwd(),
|
|
1440
|
+
bundledPackageRoot: effectivePackageRoot,
|
|
1441
|
+
enabled: claudeConfigEnabled,
|
|
1442
|
+
rules
|
|
1443
|
+
})
|
|
1444
|
+
if (!claudeConfigEnabled) {
|
|
1445
|
+
console.log('🤖 Claude-конфіг: пропущено (claude-config: false у .n-rules.json)')
|
|
1446
|
+
return
|
|
1447
|
+
}
|
|
1448
|
+
const parts = []
|
|
1449
|
+
if (result.settings) parts.push('.claude/settings.json')
|
|
1450
|
+
if (result.cursorHooks) parts.push('.cursor/hooks.json')
|
|
1451
|
+
if (result.commands.length > 0) parts.push(`${result.commands.length} slash-commands`)
|
|
1452
|
+
if (result.adrHook) parts.push('.claude/hooks/capture-decisions.sh')
|
|
1453
|
+
if (result.adrNormalizeHook) parts.push('.claude/hooks/normalize-decisions.sh')
|
|
1454
|
+
if (result.adrHookLib?.length > 0) {
|
|
1455
|
+
for (const libPath of result.adrHookLib) {
|
|
1456
|
+
parts.push(libPath)
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
if (result.gitignoreAdr) parts.push('.gitignore (adr fragment)')
|
|
1460
|
+
if (result.piExtension) parts.push('.pi/extensions/n-rules-adr/')
|
|
1461
|
+
if (parts.length > 0) {
|
|
1462
|
+
console.log(`🤖 Claude-конфіг: ${parts.join(', ')}`)
|
|
1463
|
+
}
|
|
1464
|
+
})
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
await runSyncStep('❌ Не вдалося оновити .gitignore (worktree): ', async () => {
|
|
1468
|
+
const { written } = await syncGitignoreWorktree(cwd())
|
|
1469
|
+
if (written) console.log('🌳 .gitignore (worktree): додано .worktrees/')
|
|
1470
|
+
})
|
|
1471
|
+
|
|
1472
|
+
console.log(`\n✨ Готово: ${successCount} завантажено, ${failCount} з помилками\n`)
|
|
1473
|
+
if (failCount > 0) {
|
|
1474
|
+
throw new Error(`Не вдалося завантажити ${failCount} з ${rules.length} правил`)
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Команди, що мутують проєкт у CWD і вимагають кореня репо. `undefined`/`''` —
|
|
1480
|
+
* дефолтний sync; `check` — deprecated-alias `fix`. Решта (read-only,
|
|
1481
|
+
* `--root`-команди `doc-aggregate`/`rename-yaml-extensions`,
|
|
1482
|
+
* sub-лінтери) гард не зачіпає.
|
|
1483
|
+
*/
|
|
1484
|
+
const ROOT_GUARDED_COMMANDS = new Set([undefined, '', 'lint', 'release'])
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Короткий опис дії для тексту root-guard помилки за іменем команди.
|
|
1488
|
+
* @param {string | undefined} cmd підкоманда CLI (або undefined для дефолтного sync)
|
|
1489
|
+
* @returns {string} фраза «що саме мутує CWD»
|
|
1490
|
+
*/
|
|
1491
|
+
function describeRootGuardedAction(cmd) {
|
|
1492
|
+
switch (cmd) {
|
|
1493
|
+
case undefined:
|
|
1494
|
+
case '': {
|
|
1495
|
+
return 'Дефолтна синхронізація скаффолдить .cursor/, .claude/, CLAUDE.md, .n-rules.json і робить bun install у поточному каталозі'
|
|
1496
|
+
}
|
|
1497
|
+
case 'lint': {
|
|
1498
|
+
return '`lint` за замовчуванням авто-fix лінтерів (oxfmt/eslint --fix/stylelint --fix) і конформності (--full) у поточному каталозі'
|
|
1499
|
+
}
|
|
1500
|
+
case 'release': {
|
|
1501
|
+
return '`release` бампає version і переписує CHANGELOG у поточному каталозі'
|
|
1502
|
+
}
|
|
1503
|
+
default: {
|
|
1504
|
+
return 'Команда @7n/rules мутує проєкт у поточному каталозі'
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* Довідка для `n-rules lint --help`: прапори unified lint surface
|
|
1511
|
+
* (spec 2026-06-29 fix-by-default, spec 2026-07-03 глобальна черга --full).
|
|
1512
|
+
*/
|
|
1513
|
+
function printLintHelp() {
|
|
1514
|
+
console.log(`Використання: npx @7n/rules lint [rule|concern ...] [прапори]
|
|
1515
|
+
|
|
1516
|
+
За замовчуванням лінт іде дельтою (змінені файли vs origin) і одразу
|
|
1517
|
+
виправляє (auto-fix: oxfmt/eslint --fix/stylelint --fix + LLM-ladder).
|
|
1518
|
+
|
|
1519
|
+
Прапори:
|
|
1520
|
+
--full Прогін по всьому репозиторію (конформність), не лише дельта.
|
|
1521
|
+
Один --full на машину: паралельні запуски стають у чергу
|
|
1522
|
+
й бачать живий прогрес активного прогону.
|
|
1523
|
+
--no-fix Лише детекція, без мутацій.
|
|
1524
|
+
--cwd <path> Робочий каталог замість поточного.
|
|
1525
|
+
--verbose Розширений вивід детекції та виправлення.
|
|
1526
|
+
--help, -h Ця довідка.
|
|
1527
|
+
|
|
1528
|
+
Позиційні аргументи — фільтр за назвою правила чи concern, наприклад:
|
|
1529
|
+
npx @7n/rules lint ga rego k8s docker text
|
|
1530
|
+
|
|
1531
|
+
Приклади:
|
|
1532
|
+
npx @7n/rules lint
|
|
1533
|
+
npx @7n/rules lint --full
|
|
1534
|
+
npx @7n/rules lint --no-fix eslint
|
|
1535
|
+
npx @7n/rules lint --cwd ./packages/foo --verbose
|
|
1536
|
+
`)
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// CLI: маршрутизація команд
|
|
1540
|
+
let [command, ...args] = process.argv.slice(2)
|
|
1541
|
+
|
|
1542
|
+
// Deprecated-аліаси до уніфікації lint-поверхні (spec 2026-06-29): старі
|
|
1543
|
+
// підкоманди `lint-<scope>` замінені на `lint <scope>`. Аліас тут — щоб
|
|
1544
|
+
// існуючі package.json/CI споживачів (lint-text/lint-ga тощо) не ламались
|
|
1545
|
+
// мовчки з непрозорим "Невідома команда" при мажорному бампі пакету.
|
|
1546
|
+
const LEGACY_LINT_COMMAND_ALIASES = {
|
|
1547
|
+
'lint-ga': 'ga',
|
|
1548
|
+
'lint-text': 'text',
|
|
1549
|
+
'lint-rego': 'rego',
|
|
1550
|
+
'lint-k8s': 'k8s',
|
|
1551
|
+
'lint-docker': 'docker'
|
|
1552
|
+
}
|
|
1553
|
+
if (command in LEGACY_LINT_COMMAND_ALIASES) {
|
|
1554
|
+
const scope = LEGACY_LINT_COMMAND_ALIASES[command]
|
|
1555
|
+
console.error(`⚠️ "${command}" застаріла назва команди — використовуйте "n-rules lint ${scope}"`)
|
|
1556
|
+
args = [scope, ...args]
|
|
1557
|
+
command = 'lint'
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// `lint --help`/`-h` — чиста довідка, без root-guard і без мутації devDependencies.
|
|
1561
|
+
const isLintHelp = command === 'lint' && (args.includes('--help') || args.includes('-h'))
|
|
1562
|
+
|
|
1563
|
+
try {
|
|
1564
|
+
if (isLintHelp) {
|
|
1565
|
+
printLintHelp()
|
|
1566
|
+
} else {
|
|
1567
|
+
// Root-guard до перших мутацій: дефолтний sync скаффолдить .cursor/.claude/CLAUDE.md/
|
|
1568
|
+
// .n-rules.json + bun install, а lint/release переписують файли в CWD —
|
|
1569
|
+
// усе це ключиться на cwd(). Запуск із піддиректорії git-репо (типово прямий
|
|
1570
|
+
// `bun npm/bin/n-rules.js` не з кореня) зачепив би не той каталог → STOP. Read-only та
|
|
1571
|
+
// `--root`-команди (doc-aggregate, rename-yaml-extensions) не зачіпаємо.
|
|
1572
|
+
// `lint` з явним `--cwd` — свідомий вибір кореня (MT ганяє `## Check` вузла з
|
|
1573
|
+
// node-dir усередині worktree, live e2e 2026-07-12): guard і devDeps-ensure
|
|
1574
|
+
// цілять у ЦІЛЬОВИЙ каталог, а не процесний cwd.
|
|
1575
|
+
const lintCwdIdx = command === 'lint' ? args.indexOf('--cwd') : -1
|
|
1576
|
+
const effectiveRoot = lintCwdIdx !== -1 && args[lintCwdIdx + 1] ? resolve(args[lintCwdIdx + 1]) : cwd()
|
|
1577
|
+
if (ROOT_GUARDED_COMMANDS.has(command)) {
|
|
1578
|
+
assertCwdIsProjectRoot(effectiveRoot, describeRootGuardedAction(command))
|
|
1579
|
+
}
|
|
1580
|
+
// mt-run-node — MT-екзекутор у worktree вузла: жодних side-effect-ів поза фіксом
|
|
1581
|
+
// (devDeps-ensure мутив би package.json чужого worktree).
|
|
1582
|
+
if (command !== 'mt-run-node') await ensureNRulesInRootDevDependencies(effectiveRoot)
|
|
1583
|
+
// Підкоманди-оркестратори (hook/lint/skill/adr-normalize-local/taze/release тощо)
|
|
1584
|
+
// можуть спавнити внутрішню agent/LLM-сесію — ADR Stop-hooks (capture/normalize)
|
|
1585
|
+
// мають пропустити її як технічний шум, не людську думку (spec 2026-06-30).
|
|
1586
|
+
env.ADR_HOOKS_SKIP = '1'
|
|
1587
|
+
switch (command) {
|
|
1588
|
+
case 'rename-yaml-extensions': {
|
|
1589
|
+
const code = await runRenameYamlExtensionsCli(args)
|
|
1590
|
+
if (code !== 0) {
|
|
1591
|
+
process.exitCode = 1
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
break
|
|
1595
|
+
}
|
|
1596
|
+
case 'hook': {
|
|
1597
|
+
// Thin hook entrypoint для Claude Code hooks. Делегує в runLint, перекодовує exit 1→2.
|
|
1598
|
+
// --post-tool-use PostToolUse: file_path зі stdin JSON
|
|
1599
|
+
// --stop Stop: робоче дерево vs HEAD
|
|
1600
|
+
const { runHookCli } = await import('../scripts/hook.mjs')
|
|
1601
|
+
process.exitCode = await runHookCli(args)
|
|
1602
|
+
|
|
1603
|
+
break
|
|
1604
|
+
}
|
|
1605
|
+
case 'lint': {
|
|
1606
|
+
// Unified lint surface (spec 2026-06-29). Осі: --full (весь репо vs дельта) ×
|
|
1607
|
+
// --no-fix (detect-only). Позиційні аргументи — scoped rule/concern фільтр.
|
|
1608
|
+
// Fix-by-default: detect → T0 → LLM-ladder (run-fix). --no-fix: лише detect.
|
|
1609
|
+
const cwdIdx = args.indexOf('--cwd')
|
|
1610
|
+
const cwdArg = cwdIdx === -1 ? cwd() : resolve(args[cwdIdx + 1])
|
|
1611
|
+
const rules = args.filter((a, i) => !a.startsWith('-') && !(cwdIdx !== -1 && i === cwdIdx + 1))
|
|
1612
|
+
const lintOpts = { cwd: cwdArg, full: args.includes('--full'), rules, verbose: args.includes('--verbose') }
|
|
1613
|
+
const noFix = args.includes('--no-fix')
|
|
1614
|
+
// Глобальна черга full-прогонів (spec 2026-07-03): одночасно виконується один
|
|
1615
|
+
// `lint --full` на машину, паралельні --full чекають лока і бачать чергу та
|
|
1616
|
+
// живий прогрес активного прогону; не-full запуски йдуть без лока
|
|
1617
|
+
// (див. lint-lock.mjs). Publisher пише знімки прогресу для черги.
|
|
1618
|
+
const { withGlobalLintLock, createProgressPublisher } =
|
|
1619
|
+
await import('../scripts/lib/lint-surface/lint-lock.mjs')
|
|
1620
|
+
process.exitCode = await withGlobalLintLock({ ...lintOpts, noFix }, async () => {
|
|
1621
|
+
const publisher = lintOpts.full ? createProgressPublisher() : null
|
|
1622
|
+
const runOpts = publisher ? { ...lintOpts, onProgress: publisher.onUpdate } : lintOpts
|
|
1623
|
+
try {
|
|
1624
|
+
if (noFix) {
|
|
1625
|
+
const { detectAll } = await import('../scripts/lib/lint-surface/run-detectors.mjs')
|
|
1626
|
+
// окрема змінна замість (await detectAll(...)).exitCode — no-await-expression-member (oxlint)
|
|
1627
|
+
const detectResult = await detectAll(runOpts)
|
|
1628
|
+
return detectResult.exitCode
|
|
1629
|
+
}
|
|
1630
|
+
const { runFixPipeline } = await import('../scripts/lib/lint-surface/run-fix.mjs')
|
|
1631
|
+
return await runFixPipeline(runOpts)
|
|
1632
|
+
} finally {
|
|
1633
|
+
publisher?.stop()
|
|
1634
|
+
}
|
|
1635
|
+
})
|
|
1636
|
+
|
|
1637
|
+
break
|
|
1638
|
+
}
|
|
1639
|
+
case 'taze': {
|
|
1640
|
+
// n-rules taze diff — read-only semver-diff package.json ↔ package.json.taze-bak
|
|
1641
|
+
// (root + воркспейси) для скілу n-taze: скрипт класифікує major-оновлення,
|
|
1642
|
+
// агент отримує готовий список замість ручного порівняння бекапів.
|
|
1643
|
+
const { runTazeCli } = await import('../skills/taze/js/diff.mjs')
|
|
1644
|
+
process.exitCode = await runTazeCli(args)
|
|
1645
|
+
|
|
1646
|
+
break
|
|
1647
|
+
}
|
|
1648
|
+
case 'release': {
|
|
1649
|
+
const { runReleaseCli } = await import('../rules/release/release.mjs')
|
|
1650
|
+
process.exitCode = await runReleaseCli(args)
|
|
1651
|
+
|
|
1652
|
+
break
|
|
1653
|
+
}
|
|
1654
|
+
case 'skill': {
|
|
1655
|
+
process.exitCode = await runSkillsCli(args)
|
|
1656
|
+
|
|
1657
|
+
break
|
|
1658
|
+
}
|
|
1659
|
+
case 'adr-normalize-local': {
|
|
1660
|
+
// Local-backend ADR-нормалізації: викликається з .claude/hooks/normalize-decisions.sh
|
|
1661
|
+
// як заміна single-shot LLM-виклику. Проганяє конвеєр (retrieval→edge-judge→
|
|
1662
|
+
// cluster→gen) на малій локальній моделі й друкує `{operations}` JSON у stdout.
|
|
1663
|
+
const { runAdrNormalizeLocalCli } = await import('../scripts/lib/adr/normalize-cli.mjs')
|
|
1664
|
+
process.exitCode = await runAdrNormalizeLocalCli(args)
|
|
1665
|
+
|
|
1666
|
+
break
|
|
1667
|
+
}
|
|
1668
|
+
case 'mt-run-node': {
|
|
1669
|
+
// B2 executor-міст (Фаза B): MT node_executor спавнить це для fix-вузла.
|
|
1670
|
+
// Виконує вузол нашим pi-harness (тир-канон); stdout={applied,touchedFiles}.
|
|
1671
|
+
const { runNodeCli } = await import('../scripts/lib/lint-surface/mt-run-node.mjs')
|
|
1672
|
+
process.exitCode = await runNodeCli(args)
|
|
1673
|
+
|
|
1674
|
+
break
|
|
1675
|
+
}
|
|
1676
|
+
case undefined:
|
|
1677
|
+
case '': {
|
|
1678
|
+
await runSync()
|
|
1679
|
+
|
|
1680
|
+
break
|
|
1681
|
+
}
|
|
1682
|
+
default: {
|
|
1683
|
+
console.error(`❌ Невідома команда: ${command}`)
|
|
1684
|
+
console.error(
|
|
1685
|
+
` Очікується: (без аргументів) синхронізація правил, rename-yaml-extensions, hook, adr-normalize-local, lint (включно зі scope: lint ga|rego|k8s|docker|text), taze, release, skill, doc-aggregate`
|
|
1686
|
+
)
|
|
1687
|
+
process.exitCode = 1
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
if (error instanceof ReexecHandoff) {
|
|
1693
|
+
process.exitCode = error.code
|
|
1694
|
+
} else if (error instanceof Error && error.message) {
|
|
1695
|
+
console.error(error.message)
|
|
1696
|
+
process.exitCode = 1
|
|
1697
|
+
} else {
|
|
1698
|
+
console.error(error)
|
|
1699
|
+
process.exitCode = 1
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// Pi-агент тримає TCP keep-alive сокет відкритим — явний вихід, щоб не висіти.
|
|
1704
|
+
// Відтворюємо семантику process.exit() без самого виклику (n/no-process-exit):
|
|
1705
|
+
// фіксуємо код, віддаємо 'exit'-слухачам, далі — примусове завершення через reallyExit.
|
|
1706
|
+
const exitCode = process.exitCode ?? 0
|
|
1707
|
+
process.exitCode = exitCode
|
|
1708
|
+
process.emit('exit', exitCode)
|
|
1709
|
+
process.reallyExit(exitCode)
|