@7n/rules 1.4.1 → 1.5.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/CHANGELOG.md +12 -0
- package/bin/n-rules.js +65 -17
- package/package.json +11 -1
- package/rules/abie/clean_merged_ignore_branches/concern.json +3 -0
- package/rules/docker/lint_docker_yml/concern.json +3 -0
- package/rules/js/lint_js_yml/concern.json +3 -0
- package/rules/k8s/lint_k8s_yml/concern.json +3 -0
- package/rules/npm-module/npm_publish_yml/concern.json +3 -0
- package/rules/php/lint_php_yml/concern.json +3 -0
- package/rules/python/lint_python_yml/concern.json +3 -0
- package/rules/rust/lint_rust_yml/concern.json +3 -0
- package/rules/rust/toolchain_cache/concern.json +10 -1
- package/rules/security/lint_security_yml/concern.json +3 -0
- package/rules/style/lint_style_yml/concern.json +3 -0
- package/rules/tauri/main.mdc +1 -1
- package/rules/tauri/release/release.mdc +15 -0
- package/rules/text/lint_text/concern.json +3 -0
- package/schemas/concern.json +86 -16
- package/schemas/n-rules.json +11 -1
- package/schemas/v8r-catalog.json +123 -27
- package/scripts/auto-rules.mjs +47 -6
- package/scripts/docs/auto-rules.md +3 -1
- package/scripts/docs/skills-cli.md +3 -2
- package/scripts/docs/upgrade-n-rules-and-install.md +3 -1
- package/scripts/lib/acp-runner.mjs +174 -0
- package/scripts/lib/concern-meta.mjs +8 -1
- package/scripts/lib/docs/acp-runner.md +58 -0
- package/scripts/lib/docs/concern-meta.md +1 -1
- package/scripts/lib/docs/read-n-rules-config-lite.md +3 -1
- package/scripts/lib/docs/resolve-plugins.md +17 -0
- package/scripts/lib/docs/run-conftest-batch.md +3 -1
- package/scripts/lib/lint-surface/docs/policy-lint-adapter.md +3 -1
- package/scripts/lib/lint-surface/docs/run-detectors.md +5 -1
- package/scripts/lib/lint-surface/policy-lint-adapter.mjs +2 -0
- package/scripts/lib/lint-surface/run-detectors.mjs +72 -5
- package/scripts/lib/read-n-rules-config-lite.mjs +5 -3
- package/scripts/lib/resolve-plugins.mjs +248 -0
- package/scripts/lib/run-conftest-batch.mjs +3 -1
- package/scripts/skills-cli.mjs +30 -52
- package/scripts/upgrade-n-rules-and-install.mjs +5 -4
- package/rules/ga/clean_ga_workflows/clean_ga_workflows.mdc +0 -20
- package/rules/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -112
- package/rules/ga/clean_ga_workflows/concern.json +0 -10
- package/rules/ga/clean_ga_workflows/docs/fix-clean_ga_workflows.md +0 -33
- package/rules/ga/clean_ga_workflows/docs/index.md +0 -9
- package/rules/ga/clean_ga_workflows/fix-clean_ga_workflows.mjs +0 -8
- package/rules/ga/clean_ga_workflows/template/clean-ga-workflows.yml.snippet.yml +0 -26
- package/rules/ga/clean_merged_branch/clean_merged_branch.mdc +0 -24
- package/rules/ga/clean_merged_branch/clean_merged_branch.rego +0 -163
- package/rules/ga/clean_merged_branch/concern.json +0 -10
- package/rules/ga/clean_merged_branch/docs/fix-clean_merged_branch.md +0 -27
- package/rules/ga/clean_merged_branch/docs/index.md +0 -9
- package/rules/ga/clean_merged_branch/fix-clean_merged_branch.mjs +0 -8
- package/rules/ga/clean_merged_branch/template/clean-merged-branch.yml.snippet.yml +0 -37
- package/rules/ga/docs/index.md +0 -11
- package/rules/ga/git_ai/concern.json +0 -11
- package/rules/ga/git_ai/docs/fix-git_ai.md +0 -30
- package/rules/ga/git_ai/docs/index.md +0 -9
- package/rules/ga/git_ai/fix-git_ai.mjs +0 -5
- package/rules/ga/git_ai/git_ai.mdc +0 -19
- package/rules/ga/git_ai/git_ai.rego +0 -76
- package/rules/ga/git_ai/template/git-ai.yml.snippet.yml +0 -30
- package/rules/ga/lint_ga/concern.json +0 -10
- package/rules/ga/lint_ga/docs/fix-lint_ga.md +0 -32
- package/rules/ga/lint_ga/docs/index.md +0 -9
- package/rules/ga/lint_ga/fix-lint_ga.mjs +0 -5
- package/rules/ga/lint_ga/lint_ga.mdc +0 -21
- package/rules/ga/lint_ga/lint_ga.rego +0 -132
- package/rules/ga/lint_ga/template/lint-ga.yml.snippet.yml +0 -41
- package/rules/ga/main.json +0 -1
- package/rules/ga/main.mdc +0 -17
- package/rules/ga/vscode_extensions/concern.json +0 -11
- package/rules/ga/vscode_extensions/docs/fix-vscode_extensions.md +0 -25
- package/rules/ga/vscode_extensions/docs/index.md +0 -11
- package/rules/ga/vscode_extensions/fix-vscode_extensions.mjs +0 -1
- package/rules/ga/vscode_extensions/template/extensions.json.snippet.json +0 -1
- package/rules/ga/vscode_extensions/vscode_extensions.mdc +0 -9
- package/rules/ga/vscode_extensions/vscode_extensions.rego +0 -18
- package/rules/ga/vscode_settings/concern.json +0 -11
- package/rules/ga/vscode_settings/docs/fix-vscode_settings.md +0 -26
- package/rules/ga/vscode_settings/docs/index.md +0 -9
- package/rules/ga/vscode_settings/fix-vscode_settings.mjs +0 -5
- package/rules/ga/vscode_settings/template/settings.json.snippet.json +0 -1
- package/rules/ga/vscode_settings/vscode_settings.mdc +0 -9
- package/rules/ga/vscode_settings/vscode_settings.rego +0 -22
- package/rules/ga/workflow_common/concern.json +0 -9
- package/rules/ga/workflow_common/template/uses-min-versions.snippet.json +0 -4
- package/rules/ga/workflow_common/workflow_common.mdc +0 -18
- package/rules/ga/workflow_common/workflow_common.rego +0 -288
- package/rules/ga/workflows/concern.json +0 -8
- package/rules/ga/workflows/docs/fix-workflows.md +0 -37
- package/rules/ga/workflows/docs/main.md +0 -38
- package/rules/ga/workflows/fix-workflows.mjs +0 -212
- package/rules/ga/workflows/main.mjs +0 -433
- package/rules/ga/workflows/workflows.mdc +0 -33
- package/rules/ga/zizmor_yml/concern.json +0 -11
- package/rules/ga/zizmor_yml/docs/fix-zizmor_yml.md +0 -28
- package/rules/ga/zizmor_yml/docs/index.md +0 -9
- package/rules/ga/zizmor_yml/fix-zizmor_yml.mjs +0 -3
- package/rules/ga/zizmor_yml/template/zizmor.yml.snippet.yml +0 -5
- package/rules/ga/zizmor_yml/zizmor_yml.mdc +0 -9
- package/rules/ga/zizmor_yml/zizmor_yml.rego +0 -27
package/schemas/v8r-catalog.json
CHANGED
|
@@ -6,139 +6,220 @@
|
|
|
6
6
|
"name": "n-rules.json",
|
|
7
7
|
"description": "Конфігурація @7n/rules (.n-rules.json у корені репозиторію)",
|
|
8
8
|
"url": "n-rules.json",
|
|
9
|
-
"fileMatch": [
|
|
9
|
+
"fileMatch": [
|
|
10
|
+
".n-rules.json",
|
|
11
|
+
"**/.n-rules.json",
|
|
12
|
+
".n-rules.example.json",
|
|
13
|
+
"**/.n-rules.example.json"
|
|
14
|
+
]
|
|
10
15
|
},
|
|
11
16
|
{
|
|
12
17
|
"name": "any.json",
|
|
13
18
|
"description": "Заглушка-схема (приймає будь-який JSON) — bespoke tool-конфіги й one-off артефакти без офіційної схеми",
|
|
14
19
|
"url": "any.json",
|
|
15
|
-
"fileMatch": [
|
|
20
|
+
"fileMatch": [
|
|
21
|
+
".licensee.json",
|
|
22
|
+
"**/.licensee.json",
|
|
23
|
+
"docs/specs/**/*.json",
|
|
24
|
+
"docs/plans/**/*.json",
|
|
25
|
+
"any.json"
|
|
26
|
+
]
|
|
16
27
|
},
|
|
17
28
|
{
|
|
18
29
|
"name": "rego-target.json",
|
|
19
30
|
"description": "Маніфест rego-полісі (npm/rules/<id>/policy/<name>/target.json) — які файли проєкту фідити в conftest",
|
|
20
31
|
"url": "target.json",
|
|
21
|
-
"fileMatch": [
|
|
32
|
+
"fileMatch": [
|
|
33
|
+
"npm/rules/*/policy/*/target.json",
|
|
34
|
+
"rules/*/policy/*/target.json",
|
|
35
|
+
"plugins/*/rules/*/policy/*/target.json"
|
|
36
|
+
]
|
|
22
37
|
},
|
|
23
38
|
{
|
|
24
39
|
"name": "tsconfig",
|
|
25
40
|
"description": "TypeScript config (tsconfig.json та tsconfig.*.json) у всьому проєкті",
|
|
26
41
|
"url": "vendor/tsconfig.json",
|
|
27
|
-
"fileMatch": [
|
|
42
|
+
"fileMatch": [
|
|
43
|
+
"tsconfig.json",
|
|
44
|
+
"**/tsconfig.json",
|
|
45
|
+
"tsconfig.*.json",
|
|
46
|
+
"**/tsconfig.*.json"
|
|
47
|
+
]
|
|
28
48
|
},
|
|
29
49
|
{
|
|
30
50
|
"name": "jsconfig",
|
|
31
51
|
"description": "jsconfig.json у всьому проєкті",
|
|
32
52
|
"url": "vendor/jsconfig.json",
|
|
33
|
-
"fileMatch": [
|
|
53
|
+
"fileMatch": [
|
|
54
|
+
"jsconfig.json",
|
|
55
|
+
"**/jsconfig.json"
|
|
56
|
+
]
|
|
34
57
|
},
|
|
35
58
|
{
|
|
36
59
|
"name": "n-rules skill meta",
|
|
37
60
|
"description": "Маніфест skill-пакета n-rules (npm/skills/<id>/main.json) — назва, тригери, дозволи",
|
|
38
|
-
"fileMatch": [
|
|
61
|
+
"fileMatch": [
|
|
62
|
+
"npm/skills/*/main.json",
|
|
63
|
+
"skills/*/main.json"
|
|
64
|
+
],
|
|
39
65
|
"url": "skill-meta.json"
|
|
40
66
|
},
|
|
41
67
|
{
|
|
42
68
|
"name": "n-rules rule meta",
|
|
43
69
|
"description": "Маніфест rule-пакета n-rules (npm/rules/<id>/main.json) — назва, опис, версія правила",
|
|
44
|
-
"fileMatch": [
|
|
70
|
+
"fileMatch": [
|
|
71
|
+
"npm/rules/*/main.json",
|
|
72
|
+
"rules/*/main.json",
|
|
73
|
+
"plugins/*/rules/*/main.json"
|
|
74
|
+
],
|
|
45
75
|
"url": "rule-meta.json"
|
|
46
76
|
},
|
|
47
77
|
{
|
|
48
78
|
"name": "n-rules concern meta",
|
|
49
79
|
"description": "Маніфест concern-а n-rules (npm/rules/<id>/<concern>/concern.json) — flat-concern layout: check/policy/lint-поверхні",
|
|
50
|
-
"fileMatch": [
|
|
80
|
+
"fileMatch": [
|
|
81
|
+
"npm/rules/*/*/concern.json",
|
|
82
|
+
"rules/*/*/concern.json",
|
|
83
|
+
"plugins/*/rules/*/*/concern.json"
|
|
84
|
+
],
|
|
51
85
|
"url": "concern.json"
|
|
52
86
|
},
|
|
53
87
|
{
|
|
54
88
|
"name": "package.json",
|
|
55
89
|
"description": "npm package.json у всьому проєкті",
|
|
56
90
|
"url": "vendor/package.json",
|
|
57
|
-
"fileMatch": [
|
|
91
|
+
"fileMatch": [
|
|
92
|
+
"package.json",
|
|
93
|
+
"**/package.json"
|
|
94
|
+
]
|
|
58
95
|
},
|
|
59
96
|
{
|
|
60
97
|
"name": "knip",
|
|
61
98
|
"description": "knip.json (unused-code detector config)",
|
|
62
99
|
"url": "vendor/knip.json",
|
|
63
|
-
"fileMatch": [
|
|
100
|
+
"fileMatch": [
|
|
101
|
+
"knip.json",
|
|
102
|
+
"**/knip.json"
|
|
103
|
+
]
|
|
64
104
|
},
|
|
65
105
|
{
|
|
66
106
|
"name": "jscpd",
|
|
67
107
|
"description": ".jscpd.json (copy-paste detector config)",
|
|
68
108
|
"url": "vendor/jscpd.json",
|
|
69
|
-
"fileMatch": [
|
|
109
|
+
"fileMatch": [
|
|
110
|
+
".jscpd.json",
|
|
111
|
+
"**/.jscpd.json"
|
|
112
|
+
]
|
|
70
113
|
},
|
|
71
114
|
{
|
|
72
115
|
"name": "cspell",
|
|
73
116
|
"description": ".cspell.json (spell-checker config)",
|
|
74
117
|
"url": "vendor/cspell.json",
|
|
75
|
-
"fileMatch": [
|
|
118
|
+
"fileMatch": [
|
|
119
|
+
".cspell.json",
|
|
120
|
+
"**/.cspell.json"
|
|
121
|
+
]
|
|
76
122
|
},
|
|
77
123
|
{
|
|
78
124
|
"name": "oxlintrc",
|
|
79
125
|
"description": ".oxlintrc.json (oxlint config)",
|
|
80
126
|
"url": "vendor/oxlintrc.json",
|
|
81
|
-
"fileMatch": [
|
|
127
|
+
"fileMatch": [
|
|
128
|
+
".oxlintrc.json",
|
|
129
|
+
"**/.oxlintrc.json"
|
|
130
|
+
]
|
|
82
131
|
},
|
|
83
132
|
{
|
|
84
133
|
"name": "oxfmtrc",
|
|
85
134
|
"description": ".oxfmtrc.json (oxfmt config)",
|
|
86
135
|
"url": "vendor/oxfmtrc.json",
|
|
87
|
-
"fileMatch": [
|
|
136
|
+
"fileMatch": [
|
|
137
|
+
".oxfmtrc.json",
|
|
138
|
+
"**/.oxfmtrc.json"
|
|
139
|
+
]
|
|
88
140
|
},
|
|
89
141
|
{
|
|
90
142
|
"name": "claude-code-settings",
|
|
91
143
|
"description": ".claude/settings.json (Claude Code hooks/permissions)",
|
|
92
144
|
"url": "vendor/claude-code-settings.json",
|
|
93
|
-
"fileMatch": [
|
|
145
|
+
"fileMatch": [
|
|
146
|
+
".claude/settings.json",
|
|
147
|
+
"**/.claude/settings.json"
|
|
148
|
+
]
|
|
94
149
|
},
|
|
95
150
|
{
|
|
96
151
|
"name": "codex-hooks",
|
|
97
152
|
"description": ".codex/hooks.json (Codex CLI hooks)",
|
|
98
153
|
"url": "vendor/codex-hooks.json",
|
|
99
|
-
"fileMatch": [
|
|
154
|
+
"fileMatch": [
|
|
155
|
+
".codex/hooks.json",
|
|
156
|
+
"**/.codex/hooks.json"
|
|
157
|
+
]
|
|
100
158
|
},
|
|
101
159
|
{
|
|
102
160
|
"name": "mcp",
|
|
103
161
|
"description": ".mcp.json (MCP-сервери проєкту: Claude Code/Cursor)",
|
|
104
162
|
"url": "vendor/mcp.json",
|
|
105
|
-
"fileMatch": [
|
|
163
|
+
"fileMatch": [
|
|
164
|
+
".mcp.json",
|
|
165
|
+
"**/.mcp.json"
|
|
166
|
+
]
|
|
106
167
|
},
|
|
107
168
|
{
|
|
108
169
|
"name": "marksman",
|
|
109
170
|
"description": ".marksman.toml — офіційної схеми нема (permissive any.json)",
|
|
110
171
|
"url": "any.json",
|
|
111
|
-
"fileMatch": [
|
|
172
|
+
"fileMatch": [
|
|
173
|
+
".marksman.toml",
|
|
174
|
+
"**/.marksman.toml"
|
|
175
|
+
]
|
|
112
176
|
},
|
|
113
177
|
{
|
|
114
178
|
"name": "cargo",
|
|
115
179
|
"description": "Cargo.toml (маніфест Rust-крейта/воркспейса)",
|
|
116
180
|
"url": "vendor/cargo.json",
|
|
117
|
-
"fileMatch": [
|
|
181
|
+
"fileMatch": [
|
|
182
|
+
"Cargo.toml",
|
|
183
|
+
"**/Cargo.toml"
|
|
184
|
+
]
|
|
118
185
|
},
|
|
119
186
|
{
|
|
120
187
|
"name": "cargo-mutants",
|
|
121
188
|
"description": ".cargo/mutants.toml (конфігурація cargo-mutants)",
|
|
122
189
|
"url": "vendor/cargo-mutants-config.json",
|
|
123
|
-
"fileMatch": [
|
|
190
|
+
"fileMatch": [
|
|
191
|
+
".cargo/mutants.toml",
|
|
192
|
+
"**/.cargo/mutants.toml"
|
|
193
|
+
]
|
|
124
194
|
},
|
|
125
195
|
{
|
|
126
196
|
"name": "cargo-deny",
|
|
127
197
|
"description": "deny.toml (cargo-deny) — у schemastore схеми нема (permissive any.json)",
|
|
128
198
|
"url": "any.json",
|
|
129
|
-
"fileMatch": [
|
|
199
|
+
"fileMatch": [
|
|
200
|
+
"deny.toml",
|
|
201
|
+
"**/deny.toml"
|
|
202
|
+
]
|
|
130
203
|
},
|
|
131
204
|
{
|
|
132
205
|
"name": "tauri-conf-overlay",
|
|
133
206
|
"description": "tauri.conf.<variant>.json — частковий merge-overlay базового tauri.conf.json (повна tauri-схема відхиляє partial), тому permissive any.json",
|
|
134
207
|
"url": "any.json",
|
|
135
|
-
"fileMatch": [
|
|
208
|
+
"fileMatch": [
|
|
209
|
+
"tauri.conf.*.json",
|
|
210
|
+
"**/tauri.conf.*.json"
|
|
211
|
+
]
|
|
136
212
|
},
|
|
137
213
|
{
|
|
138
214
|
"name": "github-action",
|
|
139
215
|
"description": "action.yml/action.yaml (GitHub composite/JS actions)",
|
|
140
216
|
"url": "vendor/github-action.json",
|
|
141
|
-
"fileMatch": [
|
|
217
|
+
"fileMatch": [
|
|
218
|
+
"action.yml",
|
|
219
|
+
"**/action.yml",
|
|
220
|
+
"action.yaml",
|
|
221
|
+
"**/action.yaml"
|
|
222
|
+
]
|
|
142
223
|
},
|
|
143
224
|
{
|
|
144
225
|
"name": "github-workflow",
|
|
@@ -155,25 +236,40 @@
|
|
|
155
236
|
"name": "zizmor",
|
|
156
237
|
"description": ".github/zizmor.yml (конфігурація zizmor — статичний аналізатор GitHub Actions)",
|
|
157
238
|
"url": "vendor/zizmor.json",
|
|
158
|
-
"fileMatch": [
|
|
239
|
+
"fileMatch": [
|
|
240
|
+
".github/zizmor.yml",
|
|
241
|
+
"**/.github/zizmor.yml",
|
|
242
|
+
".github/zizmor.yaml",
|
|
243
|
+
"**/.github/zizmor.yaml"
|
|
244
|
+
]
|
|
159
245
|
},
|
|
160
246
|
{
|
|
161
247
|
"name": "bunfig",
|
|
162
248
|
"description": "bunfig.toml (конфігурація Bun runtime)",
|
|
163
249
|
"url": "vendor/bunfig.json",
|
|
164
|
-
"fileMatch": [
|
|
250
|
+
"fileMatch": [
|
|
251
|
+
"bunfig.toml",
|
|
252
|
+
"**/bunfig.toml"
|
|
253
|
+
]
|
|
165
254
|
},
|
|
166
255
|
{
|
|
167
256
|
"name": "mt-config",
|
|
168
257
|
"description": ".mt.json / .mt-override.json — конфігурація @7n/mt (Nitra MT)",
|
|
169
258
|
"url": "vendor/mt-config.json",
|
|
170
|
-
"fileMatch": [
|
|
259
|
+
"fileMatch": [
|
|
260
|
+
".mt.json",
|
|
261
|
+
"**/.mt.json",
|
|
262
|
+
"**/.mt-override.json"
|
|
263
|
+
]
|
|
171
264
|
},
|
|
172
265
|
{
|
|
173
266
|
"name": "layers-config",
|
|
174
267
|
"description": "Конфіг рушія шарової документації @7n/layers (<docsDir>/layers.json, напр. npm/docs/layers.json у репо mt)",
|
|
175
268
|
"url": "layers-config.json",
|
|
176
|
-
"fileMatch": [
|
|
269
|
+
"fileMatch": [
|
|
270
|
+
"**/docs/layers.json",
|
|
271
|
+
"layers.json"
|
|
272
|
+
]
|
|
177
273
|
}
|
|
178
274
|
]
|
|
179
275
|
}
|
package/scripts/auto-rules.mjs
CHANGED
|
@@ -73,6 +73,43 @@ export function discoverRuleAutoActivation(rulesDir = RULES_DIR) {
|
|
|
73
73
|
|
|
74
74
|
const RULE_AUTO_ACTIVATION = discoverRuleAutoActivation()
|
|
75
75
|
|
|
76
|
+
/** Кеш агрегованої активації для multi-dir прогонів: ключ — join(dirs). */
|
|
77
|
+
const AGGREGATED_ACTIVATION_CACHE = new Map()
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Агрегована мапа активації по кількох rules-каталогах (ядро + плагіни): правила
|
|
81
|
+
* зливаються за id, перший власник виграє (порядок каталогів = пріоритет).
|
|
82
|
+
* Без `rulesDirs` — вбудовані module-level константи (шлях без плагінів).
|
|
83
|
+
* @param {string[]} [rulesDirs] упорядковані rules-каталоги
|
|
84
|
+
* @returns {{ activation: Record<string, import('./lib/rule-meta.mjs').RuleAutoSpec>, order: string[], dependencies: Record<string, readonly string[]> }} активація, порядок, залежності
|
|
85
|
+
*/
|
|
86
|
+
export function getRuleAutoActivation(rulesDirs) {
|
|
87
|
+
if (!Array.isArray(rulesDirs) || rulesDirs.length === 0) {
|
|
88
|
+
return { activation: RULE_AUTO_ACTIVATION, order: AUTO_RULE_ORDER, dependencies: AUTO_RULE_DEPENDENCIES }
|
|
89
|
+
}
|
|
90
|
+
const key = rulesDirs.join('\u{0}')
|
|
91
|
+
const cached = AGGREGATED_ACTIVATION_CACHE.get(key)
|
|
92
|
+
if (cached) return cached
|
|
93
|
+
/** @type {Record<string, import('./lib/rule-meta.mjs').RuleAutoSpec>} */
|
|
94
|
+
const activation = {}
|
|
95
|
+
for (const dir of rulesDirs) {
|
|
96
|
+
for (const [id, spec] of Object.entries(discoverRuleAutoActivation(dir))) {
|
|
97
|
+
if (!(id in activation)) activation[id] = spec
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const order = Object.freeze(Object.keys(activation).toSorted((a, b) => a.localeCompare(b)))
|
|
101
|
+
const dependencies = Object.freeze(
|
|
102
|
+
Object.fromEntries(
|
|
103
|
+
Object.entries(activation)
|
|
104
|
+
.filter(([, s]) => 'rules' in s)
|
|
105
|
+
.map(([id, s]) => [id, Object.freeze(/** @type {{rules:string[]}} */ (s).rules)])
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
const result = { activation, order, dependencies }
|
|
109
|
+
AGGREGATED_ACTIVATION_CACHE.set(key, result)
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
76
113
|
/** Стабільний алфавітний порядок (замість хардкод-масиву). */
|
|
77
114
|
export const AUTO_RULE_ORDER = Object.freeze(Object.keys(RULE_AUTO_ACTIVATION).toSorted((a, b) => a.localeCompare(b)))
|
|
78
115
|
|
|
@@ -279,13 +316,14 @@ async function collectRepoPaths(root) {
|
|
|
279
316
|
* стежити за порядком викликів `addRule`.
|
|
280
317
|
* @param {string[]} detectedRules уже зібрані id правил (мутується через addRule)
|
|
281
318
|
* @param {(ruleId: string) => void} addRule callback із спільної фабрики (поважає `disable-rules` і дублі)
|
|
319
|
+
* @param {Record<string, readonly string[]>} [dependencies] граф залежностей (дефолт — вбудований)
|
|
282
320
|
* @returns {void}
|
|
283
321
|
*/
|
|
284
|
-
function resolveRuleDependencies(detectedRules, addRule) {
|
|
322
|
+
function resolveRuleDependencies(detectedRules, addRule, dependencies = AUTO_RULE_DEPENDENCIES) {
|
|
285
323
|
let changed = true
|
|
286
324
|
while (changed) {
|
|
287
325
|
changed = false
|
|
288
|
-
for (const [ruleId, deps] of Object.entries(
|
|
326
|
+
for (const [ruleId, deps] of Object.entries(dependencies)) {
|
|
289
327
|
if (detectedRules.includes(ruleId)) continue
|
|
290
328
|
if (deps.every(d => detectedRules.includes(d))) {
|
|
291
329
|
const before = detectedRules.length
|
|
@@ -332,14 +370,17 @@ function specMatches(spec, ctx) {
|
|
|
332
370
|
* @param {string[]} params.availableRules перелік доступних правил з пакету
|
|
333
371
|
* @param {unknown} params.packageJsonParsed кореневий package.json (розпарсений) або null
|
|
334
372
|
* @param {string[]} [params.disableRules] список `disable-rules` з конфігу
|
|
373
|
+
* @param {string[]} [params.rulesDirs] rules-каталоги (ядро + плагіни); без них — вбудований
|
|
335
374
|
* @returns {Promise<{ rules: string[] }>} список id у стабільному порядку (за `AUTO_RULE_ORDER`)
|
|
336
375
|
*/
|
|
337
376
|
export async function detectAutoRules({
|
|
338
377
|
root,
|
|
339
378
|
availableRules,
|
|
340
379
|
packageJsonParsed,
|
|
341
|
-
disableRules = DEFAULT_DISABLED_LIST
|
|
380
|
+
disableRules = DEFAULT_DISABLED_LIST,
|
|
381
|
+
rulesDirs
|
|
342
382
|
}) {
|
|
383
|
+
const { activation, order, dependencies } = getRuleAutoActivation(rulesDirs)
|
|
343
384
|
const facts = await collectAutoRuleFacts(root)
|
|
344
385
|
const paths = await collectRepoPaths(root)
|
|
345
386
|
const normalizedRules = new Set(availableRules.map(r => r.trim().toLowerCase()))
|
|
@@ -357,13 +398,13 @@ export async function detectAutoRules({
|
|
|
357
398
|
detectedRules.push(ruleId)
|
|
358
399
|
}
|
|
359
400
|
|
|
360
|
-
for (const [ruleId, spec] of Object.entries(
|
|
401
|
+
for (const [ruleId, spec] of Object.entries(activation)) {
|
|
361
402
|
if ('rules' in spec) continue
|
|
362
403
|
if (await specMatches(spec, { root, facts, paths, packageJsonParsed })) addRule(ruleId)
|
|
363
404
|
}
|
|
364
|
-
resolveRuleDependencies(detectedRules, addRule)
|
|
405
|
+
resolveRuleDependencies(detectedRules, addRule, dependencies)
|
|
365
406
|
|
|
366
|
-
const rules =
|
|
407
|
+
const rules = order.filter(r => detectedRules.includes(r))
|
|
367
408
|
return { rules }
|
|
368
409
|
}
|
|
369
410
|
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: auto-rules.mjs
|
|
4
4
|
resource: npm/scripts/auto-rules.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: d4a1b2da
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 90
|
|
9
9
|
---
|
|
@@ -32,3 +32,5 @@ mergeConfigWithAutoDetected — Обновлює конфігурацію, до
|
|
|
32
32
|
|
|
33
33
|
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
34
34
|
- Свідомо пропускає шляхи: `.git`, `node_modules`.
|
|
35
|
+
|
|
36
|
+
**Multi-dir (плагіни):** `getRuleAutoActivation(rulesDirs)` агрегує активацію по кількох rules-каталогах (ядро перше, перший власник виграє, кеш на процес); `detectAutoRules` приймає опційний `rulesDirs`.
|
|
@@ -9,7 +9,7 @@ docgen:
|
|
|
9
9
|
|
|
10
10
|
## Огляд
|
|
11
11
|
|
|
12
|
-
Цей модуль забезпечує керування функціоналом скілів пакета `@7n/rules`. Він каталогізує доступні скіли на основі наявності файлу `SKILL.md` у відповідному пакеті. Для виконання скілу збирається контекст, що включає інструкції скілу та інформацію з конфігураційних файлів проєкту: `package.json`, `tsconfig.json`, `.n-rules.json` та `main.json`. Система дозволяє або вивести список доступних скілів (`npx @7n/rules skill list`), або ініціювати виконання обраного скілу (наприклад, `npx @7n/rules skill pi taze`), з можливістю передачі додаткових аргументів. Пріоритетним механізмом виконання є інтеграція з вбудованим pi-агентом; поряд з ним підтримуються повноцінні зовнішні
|
|
12
|
+
Цей модуль забезпечує керування функціоналом скілів пакета `@7n/rules`. Він каталогізує доступні скіли на основі наявності файлу `SKILL.md` у відповідному пакеті. Для виконання скілу збирається контекст, що включає інструкції скілу та інформацію з конфігураційних файлів проєкту: `package.json`, `tsconfig.json`, `.n-rules.json` та `main.json`. Система дозволяє або вивести список доступних скілів (`npx @7n/rules skill list`), або ініціювати виконання обраного скілу (наприклад, `npx @7n/rules skill pi taze`), з можливістю передачі додаткових аргументів. Пріоритетним механізмом виконання є інтеграція з вбудованим pi-агентом; поряд з ним підтримуються повноцінні зовнішні ACP-агенти (`cursor`, `codex`) і deprecated `claude`.
|
|
13
13
|
|
|
14
14
|
## Поведінка
|
|
15
15
|
|
|
@@ -30,6 +30,7 @@ runSkillsCli виконує логіку командного інтерфейс
|
|
|
30
30
|
|
|
31
31
|
## Гарантії поведінки
|
|
32
32
|
|
|
33
|
-
- Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або
|
|
33
|
+
- Сам модуль лише читає файли й збирає промпт; **виконання** делегується агенту: `pi` (вбудований, мутує дерево, запускає bash) або зовнішньому ACP-агенту `cursor`/`codex`/`claude` через `runAcpRunner` (`./lib/acp-runner.mjs`) — JSON-RPC поверх stdio (`cursor-agent acp`, бандловані адаптери `@agentclientprotocol/codex-acp` і `@agentclientprotocol/claude-agent-acp`), а не сирий `stdin`/`stdout`-піпінг.
|
|
34
34
|
- Тира моделі для `pi`-runner береться з `main.json.tier` скіла (дефолт `max`).
|
|
35
|
+
- ACP-раннер автоапрувляє `session/request_permission` (без інтерактивних питань) — паритет із колишнім non-interactive режимом; `Client`-реалізація читає/пише файли напряму через `node:fs`.
|
|
35
36
|
- Лише `claude` — deprecated: друкує попередження й лишається як fallback, доки не налаштовано pi-модель. `cursor`/`codex` — повноцінні раннери без попередження.
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: upgrade-n-rules-and-install.mjs
|
|
4
4
|
resource: npm/scripts/upgrade-n-rules-and-install.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: f33841b9
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 100
|
|
9
9
|
issues: judge:inaccurate:0.98
|
|
@@ -34,3 +34,5 @@ upgradeNRulesToLatestAndBunInstall — Оновлює `@7n/rules` до оста
|
|
|
34
34
|
- Свідомо пропускає шляхи: `node_modules`.
|
|
35
35
|
|
|
36
36
|
**Міграція перейменування:** `migrateLegacyDependencyName` переносить legacy-ключ `@nitra/cursor` → `@7n/rules` у тій самій секції package.json перед оновленням версії; якщо registry ще не має `@7n/rules` (404 до першої публікації), версія береться з package.json пакету поточного процесу (fallback).
|
|
37
|
+
|
|
38
|
+
**packageName:** `resolveInstalledPackageRoot(projectRoot, fallbackPackageRoot, packageName)` — третій параметр (дефолт `@7n/rules`) дозволяє резолвити встановлені плагіни.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Виконання скіла через зовнішнього ACP-агента (Agent Client Protocol,
|
|
3
|
+
* agentclientprotocol.com) — JSON-RPC поверх stdio замість сирого
|
|
4
|
+
* `stdin`/`stdout`-піпінгу CLI.
|
|
5
|
+
*
|
|
6
|
+
* Заміняє колишній `runLlmCli` (spawnSync + `-p`/`exec -`) для `cursor`/`codex`/`claude`:
|
|
7
|
+
* підʼєднання йде через офіційний TS SDK `@zed-industries/agent-client-protocol`,
|
|
8
|
+
* а дозволи на tool-calls (`session/request_permission`) автоапрувляться без участі
|
|
9
|
+
* людини — паритет із сьогоднішнім non-interactive `-p`-режимом (full user-trust,
|
|
10
|
+
* без write-guard, як задокументовано в `@7n/llm-lib/agent-skill`).
|
|
11
|
+
*
|
|
12
|
+
* `cursor` — нативний ACP-режим самого `cursor-agent` (`cursor-agent acp`).
|
|
13
|
+
* `codex`/`claude` — офіційні адаптери (`@agentclientprotocol/codex-acp`,
|
|
14
|
+
* `@agentclientprotocol/claude-agent-acp`), що бандлять свій рушій — зовнішній
|
|
15
|
+
* бінарник `codex`/`claude` у PATH більше не потрібен.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { spawn } from 'node:child_process'
|
|
19
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
20
|
+
import { createRequire } from 'node:module'
|
|
21
|
+
import { dirname, join } from 'node:path'
|
|
22
|
+
import { stdout } from 'node:process'
|
|
23
|
+
import { Readable, Writable } from 'node:stream'
|
|
24
|
+
|
|
25
|
+
const require = createRequire(import.meta.url)
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Команда запуску ACP-агента на провайдер. `cursor` — зовнішній бінарник у PATH;
|
|
29
|
+
* `codex`/`claude` — резолвляться з `bin`-запису бандлованого npm-пакета-адаптера.
|
|
30
|
+
* @type {Record<'cursor' | 'codex' | 'claude', { bin: string, args: string[] } | { adapterPackage: string }>}
|
|
31
|
+
*/
|
|
32
|
+
export const ACP_AGENT_COMMANDS = {
|
|
33
|
+
cursor: { bin: 'cursor-agent', args: ['acp'] },
|
|
34
|
+
codex: { adapterPackage: '@agentclientprotocol/codex-acp' },
|
|
35
|
+
claude: { adapterPackage: '@agentclientprotocol/claude-agent-acp' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `StopReason` ACP-прогону → exit code CLI-скіла.
|
|
40
|
+
* @param {string} stopReason `end_turn`|`max_tokens`|`max_turn_requests`|`refusal`|`cancelled`
|
|
41
|
+
* @returns {0 | 1} 0 — успішне завершення (`end_turn`), 1 — усе інше
|
|
42
|
+
*/
|
|
43
|
+
export function stopReasonToExitCode(stopReason) {
|
|
44
|
+
return stopReason === 'end_turn' ? 0 : 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Резолвить абсолютний шлях до bin-файлу бандлованого ACP-адаптера з його `package.json`.
|
|
49
|
+
* @param {string} adapterPackage назва npm-пакета адаптера (`@agentclientprotocol/codex-acp`, …)
|
|
50
|
+
* @returns {string} абсолютний шлях до виконуваного файлу адаптера
|
|
51
|
+
*/
|
|
52
|
+
export function resolveAdapterBin(adapterPackage) {
|
|
53
|
+
const pkgJsonPath = require.resolve(`${adapterPackage}/package.json`)
|
|
54
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
|
|
55
|
+
const binField = pkg.bin
|
|
56
|
+
const binRelPath = typeof binField === 'string' ? binField : Object.values(binField)[0]
|
|
57
|
+
return join(dirname(pkgJsonPath), /** @type {string} */ (binRelPath))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Обирає `PermissionOption` без участі людини: `allow_always` > `allow_once` > перша опція.
|
|
62
|
+
* Паритет із non-interactive `-p`/`exec -` — скіл є явною user-invocation, тож дозволи
|
|
63
|
+
* не питаються інтерактивно (див. `@7n/llm-lib/agent-skill` — той самий full-trust режим).
|
|
64
|
+
* @param {{ optionId: string, kind: string }[]} options варіанти дозволу з `RequestPermissionRequest`
|
|
65
|
+
* @returns {string} `optionId` обраного варіанту
|
|
66
|
+
*/
|
|
67
|
+
export function pickAutoPermissionOptionId(options) {
|
|
68
|
+
const byKind = kind => options.find(option => option.kind === kind)
|
|
69
|
+
const chosen = byKind('allow_always') ?? byKind('allow_once') ?? options[0]
|
|
70
|
+
return chosen.optionId
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* ACP `Client` для скіл-раннера: автоапрув дозволів, стрім тексту в stdout,
|
|
75
|
+
* пряма реалізація файлового вводу-виводу поверх `node:fs` (full-trust режим).
|
|
76
|
+
*/
|
|
77
|
+
class AcpSkillClient {
|
|
78
|
+
/**
|
|
79
|
+
* @param {{ out: (chunk: string) => void }} deps вивід тексту (парність зі streaming `-p`)
|
|
80
|
+
*/
|
|
81
|
+
constructor({ out }) {
|
|
82
|
+
this.out = out
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {{ options: { optionId: string, kind: string }[] }} params запит дозволу від агента
|
|
87
|
+
* @returns {Promise<{ outcome: { outcome: 'selected', optionId: string } }>} автообраний варіант
|
|
88
|
+
*/
|
|
89
|
+
requestPermission(params) {
|
|
90
|
+
return Promise.resolve({ outcome: { outcome: 'selected', optionId: pickAutoPermissionOptionId(params.options) } })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {{ update: { sessionUpdate: string, content?: { type: string, text?: string } } }} params подія прогресу сесії
|
|
95
|
+
* @returns {Promise<void>} стрімить текстові дельти в stdout; інші типи подій ігноруються
|
|
96
|
+
*/
|
|
97
|
+
sessionUpdate(params) {
|
|
98
|
+
const { update } = params
|
|
99
|
+
if (update.sessionUpdate === 'agent_message_chunk' && update.content?.type === 'text') {
|
|
100
|
+
this.out(update.content.text ?? '')
|
|
101
|
+
}
|
|
102
|
+
return Promise.resolve()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {{ path: string }} params шлях до файлу
|
|
107
|
+
* @returns {Promise<{ content: string }>} вміст файлу
|
|
108
|
+
*/
|
|
109
|
+
readTextFile(params) {
|
|
110
|
+
return Promise.resolve({ content: readFileSync(params.path, 'utf8') })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {{ path: string, content: string }} params шлях і новий вміст файлу
|
|
115
|
+
* @returns {Promise<Record<string, never>>} порожня відповідь (успіх)
|
|
116
|
+
*/
|
|
117
|
+
writeTextFile(params) {
|
|
118
|
+
writeFileSync(params.path, params.content, 'utf8')
|
|
119
|
+
return Promise.resolve({})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Виконує скіл через зовнішнього ACP-агента (`cursor`/`codex`/`claude`).
|
|
125
|
+
* @param {'cursor' | 'codex' | 'claude'} kind провайдер
|
|
126
|
+
* @param {string} prompt готовий промпт скіла
|
|
127
|
+
* @param {string} projectDir робочий каталог сесії агента
|
|
128
|
+
* @param {(line: string) => void} logError вивід помилок
|
|
129
|
+
* @param {{ acp?: object, spawnFn?: typeof spawn, out?: (chunk: string) => void, resolveAdapterBin?: (pkg: string) => string, isBinaryInPath?: (bin: string) => boolean }} [deps] інжекти для тестів
|
|
130
|
+
* @returns {Promise<number>} exit code (0 — `end_turn`, 1 — інакше)
|
|
131
|
+
*/
|
|
132
|
+
export async function runAcpRunner(kind, prompt, projectDir, logError, deps = {}) {
|
|
133
|
+
const spawnFn = deps.spawnFn ?? spawn
|
|
134
|
+
const out = deps.out ?? (chunk => stdout.write(chunk))
|
|
135
|
+
const resolveBin = deps.resolveAdapterBin ?? resolveAdapterBin
|
|
136
|
+
|
|
137
|
+
const command = ACP_AGENT_COMMANDS[kind]
|
|
138
|
+
const { bin, args } =
|
|
139
|
+
'adapterPackage' in command
|
|
140
|
+
? { bin: process.execPath, args: [resolveBin(command.adapterPackage)] }
|
|
141
|
+
: command
|
|
142
|
+
|
|
143
|
+
if ('bin' in command && deps.isBinaryInPath && !deps.isBinaryInPath(command.bin)) {
|
|
144
|
+
throw new Error(`\`${command.bin}\` not found in PATH. Install ${kind} CLI (ACP mode) or use \`skill pi\`.`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const acp = deps.acp ?? (await import('@zed-industries/agent-client-protocol'))
|
|
148
|
+
const child = spawnFn(bin, args, { cwd: projectDir, stdio: ['pipe', 'pipe', 'inherit'], env: process.env })
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const input = Writable.toWeb(child.stdin)
|
|
152
|
+
const output = /** @type {ReadableStream<Uint8Array>} */ (Readable.toWeb(child.stdout))
|
|
153
|
+
const client = new AcpSkillClient({ out })
|
|
154
|
+
const stream = acp.ndJsonStream(input, output)
|
|
155
|
+
const connection = new acp.ClientSideConnection(() => client, stream)
|
|
156
|
+
|
|
157
|
+
await connection.initialize({
|
|
158
|
+
protocolVersion: acp.PROTOCOL_VERSION,
|
|
159
|
+
clientCapabilities: { fs: { readTextFile: true, writeTextFile: true } }
|
|
160
|
+
})
|
|
161
|
+
const session = await connection.newSession({ cwd: projectDir, mcpServers: [] })
|
|
162
|
+
const result = await connection.prompt({
|
|
163
|
+
sessionId: session.sessionId,
|
|
164
|
+
prompt: [{ type: 'text', text: prompt }]
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (result.stopReason !== 'end_turn') {
|
|
168
|
+
logError(`acp ${kind}: stopReason=${result.stopReason}`)
|
|
169
|
+
}
|
|
170
|
+
return stopReasonToExitCode(result.stopReason)
|
|
171
|
+
} finally {
|
|
172
|
+
child.kill()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -27,6 +27,8 @@ import { join } from 'node:path'
|
|
|
27
27
|
/**
|
|
28
28
|
* @typedef {object} ConcernMeta
|
|
29
29
|
* @property {string} name ім'я concern-а (= basename каталогу)
|
|
30
|
+
* @property {string|undefined} requiresCapability capability з `requires.capability` — концерн
|
|
31
|
+
* активний лише коли якийсь плагін декларує її у своєму маніфесті (напр. `ci:github`)
|
|
30
32
|
* @property {string} dir абсолютний шлях до каталогу concern-а
|
|
31
33
|
* @property {boolean} check чи є JS check поверхня
|
|
32
34
|
* @property {PolicySurface|undefined} policy policy-поверхня concern-а (rego/template) або undefined.
|
|
@@ -112,7 +114,12 @@ export async function readConcernMeta(concernDir, name) {
|
|
|
112
114
|
|
|
113
115
|
if (!check && !policy && !lint) return null
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
const requiresCapability =
|
|
118
|
+
raw.requires && typeof raw.requires === 'object' && typeof raw.requires.capability === 'string'
|
|
119
|
+
? raw.requires.capability
|
|
120
|
+
: undefined
|
|
121
|
+
|
|
122
|
+
return { name, dir: concernDir, check, policy, lint, requiresCapability, fixability: parseFixability(raw.fixability) }
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
/**
|