@nitra/cursor 12.12.0 → 12.14.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/package.json +3 -1
- package/rules/bun/docs/index.md +2 -2
- package/rules/bun/js/docs/index.md +3 -3
- package/rules/bun/main.mjs +3 -1
- package/rules/changelog/js/docs/index.md +3 -3
- package/rules/doc-files/js/docgen-ignore.mjs +6 -5
- package/rules/doc-files/js/docs/docgen-ignore.md +1 -1
- package/rules/js/js/docs/index.md +5 -5
- package/rules/js-run/js/docs/index.md +3 -3
- package/rules/python/docs/index.md +2 -2
- package/rules/python/main.mjs +8 -2
- package/rules/rust/docs/index.md +2 -2
- package/rules/rust/main.mjs +3 -1
- package/rules/style/js/docs/index.md +3 -3
- package/scripts/docs/index.md +14 -14
- package/scripts/lib/discover-checkable-rules.mjs +7 -14
- package/scripts/lib/docs/index.md +35 -35
- package/scripts/lib/docs/resolve-target-files.md +3 -3
- package/scripts/lib/fix/discover-t0-patterns.mjs +15 -48
- package/scripts/lib/fix/docs/index.md +12 -12
- package/scripts/lib/fix/docs/llm-worker.md +13 -8
- package/scripts/lib/fix/llm-worker.mjs +83 -3
- package/scripts/lib/inline-template-links.mjs +9 -17
- package/scripts/lib/list-project-rules-mdc.mjs +3 -3
- package/scripts/lib/resolve-target-files.mjs +4 -10
- package/scripts/update-blue-oak.mjs +1 -1
- package/scripts/utils/walkDir.mjs +32 -72
- package/skills/doc-aggregate/SKILL.md +1 -0
- package/skills/publish-telegram/SKILL.md +26 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [12.14.0] - 2026-06-26
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Заміна picomatch на ignore для обробки glob-ів у docgen та resolve-target-files
|
|
8
|
+
|
|
9
|
+
## [12.13.0] - 2026-06-26
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Заміна рекурсивного обходу файлів на використання globby для пошуку файлів
|
|
14
|
+
|
|
3
15
|
## [12.12.0] - 2026-06-25
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.14.0",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -54,6 +54,8 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@7n/mt": "^0.5.1",
|
|
57
|
+
"globby": "^16.0.0",
|
|
58
|
+
"ignore": "^7.0.5",
|
|
57
59
|
"oxc-parser": "^0.137.0",
|
|
58
60
|
"picomatch": "^4.0.4",
|
|
59
61
|
"smol-toml": "^1.7.0",
|
package/rules/bun/docs/index.md
CHANGED
|
@@ -6,7 +6,7 @@ resource: npm/rules/bun/js/
|
|
|
6
6
|
|
|
7
7
|
# npm/rules/bun/js
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------------------- | --------- |
|
|
11
11
|
| [fix-layout.mjs](fix-layout.md) | JS Module |
|
|
12
|
-
| [layout.mjs](layout.md)
|
|
12
|
+
| [layout.mjs](layout.md) | JS Module |
|
package/rules/bun/main.mjs
CHANGED
|
@@ -38,7 +38,9 @@ function runLicenseeSteps(cwd = process.cwd(), opts = {}) {
|
|
|
38
38
|
const configPath = join(cwd, '.licensee.json')
|
|
39
39
|
if (!existsSync(configPath)) {
|
|
40
40
|
if (readOnly) {
|
|
41
|
-
fail(
|
|
41
|
+
fail(
|
|
42
|
+
'lint-bun: licensee — немає .licensee.json; запустіть `npx @nitra/cursor fix bun` локально для генерації (bun.mdc)'
|
|
43
|
+
)
|
|
42
44
|
return reporter.getExitCode()
|
|
43
45
|
}
|
|
44
46
|
writeFileSync(configPath, DEFAULT_LICENSEE_CONFIG, 'utf8')
|
|
@@ -6,7 +6,7 @@ resource: npm/rules/changelog/js/
|
|
|
6
6
|
|
|
7
7
|
# npm/rules/changelog/js
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
11
|
-
| [consistency.mjs](consistency.md)
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ----------------------------------------- | --------- |
|
|
11
|
+
| [consistency.mjs](consistency.md) | JS Module |
|
|
12
12
|
| [fix-consistency.mjs](fix-consistency.md) | JS Module |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** @see ./docs/docgen-ignore.md */
|
|
2
|
-
import
|
|
2
|
+
import ignore from 'ignore'
|
|
3
3
|
|
|
4
4
|
/** Базовий список glob-ів для `docgen` ignore. */
|
|
5
5
|
export const DOCGEN_IGNORE_GLOBS = Object.freeze([
|
|
@@ -22,7 +22,7 @@ export const DOCGEN_IGNORE_GLOBS = Object.freeze([
|
|
|
22
22
|
'npm/rules/k8s/js/manifests.mjs'
|
|
23
23
|
])
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const ig = ignore().add(DOCGEN_IGNORE_GLOBS)
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Нормалізує відносний шлях до posix-формату для glob-matching.
|
|
@@ -36,7 +36,7 @@ function toPosixRelPath(relPath) {
|
|
|
36
36
|
/**
|
|
37
37
|
* Перевіряє, чи шлях має бути пропущений `docgen`.
|
|
38
38
|
* Для `kind = 'dir'` це працює і на піддерево каталогу, тож glob на кшталт
|
|
39
|
-
*
|
|
39
|
+
* `**\/demo/**` спрацьовує на `demo/x` під час рекурсивного обходу.
|
|
40
40
|
* @param {string} relPath відносний шлях від кореня проєкту
|
|
41
41
|
* @param {'path'|'dir'} [kind] тип перевірки (за замовчуванням `'path'`)
|
|
42
42
|
* @returns {boolean} `true`, якщо шлях ігнорується
|
|
@@ -47,7 +47,8 @@ export function isDocgenIgnored(relPath, kind = 'path') {
|
|
|
47
47
|
}
|
|
48
48
|
const posixRelPath = toPosixRelPath(relPath)
|
|
49
49
|
if (kind === 'dir') {
|
|
50
|
-
|
|
50
|
+
// `**/demo/**` не матчить `demo` напряму — перевіряємо фіктивний файл всередині
|
|
51
|
+
return ig.ignores(posixRelPath) || ig.ignores(`${posixRelPath}/__docgen__`)
|
|
51
52
|
}
|
|
52
|
-
return
|
|
53
|
+
return ig.ignores(posixRelPath)
|
|
53
54
|
}
|
|
@@ -6,10 +6,10 @@ resource: npm/rules/js/js/
|
|
|
6
6
|
|
|
7
7
|
# npm/rules/js/js
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
11
|
-
| [check.mjs](check.md)
|
|
12
|
-
| [dep-policy.mjs](dep-policy.md)
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ------------------------------------- | --------- |
|
|
11
|
+
| [check.mjs](check.md) | JS Module |
|
|
12
|
+
| [dep-policy.mjs](dep-policy.md) | JS Module |
|
|
13
13
|
| [lint-findings.mjs](lint-findings.md) | JS Module |
|
|
14
|
-
| [tooling.mjs](tooling.md)
|
|
14
|
+
| [tooling.mjs](tooling.md) | JS Module |
|
|
15
15
|
| [utils_imports.mjs](utils_imports.md) | JS Module |
|
|
@@ -6,7 +6,7 @@ resource: npm/rules/js-run/js/
|
|
|
6
6
|
|
|
7
7
|
# npm/rules/js-run/js
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| --------------------------------- | --------- |
|
|
11
11
|
| [fix-runtime.mjs](fix-runtime.md) | JS Module |
|
|
12
|
-
| [runtime.mjs](runtime.md)
|
|
12
|
+
| [runtime.mjs](runtime.md) | JS Module |
|
package/rules/python/main.mjs
CHANGED
|
@@ -128,7 +128,9 @@ export function runLintPythonSteps(cwd = process.cwd(), opts = {}) {
|
|
|
128
128
|
return true
|
|
129
129
|
}
|
|
130
130
|
const r = spawnSync(uvPath, ['run', '--frozen', 'pip-licenses', '--from=mixed', '--format=spdx-json'], {
|
|
131
|
-
cwd: cwdPath,
|
|
131
|
+
cwd: cwdPath,
|
|
132
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
133
|
+
shell: false
|
|
132
134
|
})
|
|
133
135
|
if (r.status !== 0) {
|
|
134
136
|
failF('lint-python: pip-licenses — помилка виконання')
|
|
@@ -136,7 +138,11 @@ export function runLintPythonSteps(cwd = process.cwd(), opts = {}) {
|
|
|
136
138
|
}
|
|
137
139
|
const allowed = getBronzeAndAbove()
|
|
138
140
|
let doc
|
|
139
|
-
try {
|
|
141
|
+
try {
|
|
142
|
+
doc = JSON.parse(r.stdout.toString('utf8'))
|
|
143
|
+
} catch {
|
|
144
|
+
doc = null
|
|
145
|
+
}
|
|
140
146
|
const packages = doc?.packages ?? []
|
|
141
147
|
const violations = packages.filter(pkg => {
|
|
142
148
|
const lic = pkg.licenseDeclared ?? pkg.licenseConcluded ?? 'NOASSERTION'
|
package/rules/rust/docs/index.md
CHANGED
package/rules/rust/main.mjs
CHANGED
|
@@ -89,7 +89,9 @@ function runRustLint(cwd = process.cwd(), opts = {}) {
|
|
|
89
89
|
const denyConfigPath = join(cwd, 'deny.toml')
|
|
90
90
|
if (!existsSync(denyConfigPath)) {
|
|
91
91
|
if (readOnly) {
|
|
92
|
-
fail(
|
|
92
|
+
fail(
|
|
93
|
+
'lint-rust: cargo deny — немає deny.toml; запустіть `npx @nitra/cursor fix rust` локально для генерації (rust.mdc)'
|
|
94
|
+
)
|
|
93
95
|
} else {
|
|
94
96
|
writeFileSync(denyConfigPath, generateDenyTomlLicenses(), 'utf8')
|
|
95
97
|
pass('lint-rust: cargo deny — створено deny.toml з дефолтним allowlist')
|
|
@@ -6,7 +6,7 @@ resource: npm/rules/style/js/
|
|
|
6
6
|
|
|
7
7
|
# npm/rules/style/js
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| --------------------------------- | --------- |
|
|
11
11
|
| [fix-tooling.mjs](fix-tooling.md) | JS Module |
|
|
12
|
-
| [tooling.mjs](tooling.md)
|
|
12
|
+
| [tooling.mjs](tooling.md) | JS Module |
|
package/scripts/docs/index.md
CHANGED
|
@@ -6,18 +6,18 @@ resource: npm/scripts/
|
|
|
6
6
|
|
|
7
7
|
# npm/scripts
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
11
|
-
| [auto-rules.mjs](auto-rules.md)
|
|
12
|
-
| [auto-skills.mjs](auto-skills.md)
|
|
13
|
-
| [build-agents-commands.mjs](build-agents-commands.md)
|
|
14
|
-
| [cli-entry.mjs](cli-entry.md)
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ----------------------------------------------------------------------------------- | --------- |
|
|
11
|
+
| [auto-rules.mjs](auto-rules.md) | JS Module |
|
|
12
|
+
| [auto-skills.mjs](auto-skills.md) | JS Module |
|
|
13
|
+
| [build-agents-commands.mjs](build-agents-commands.md) | JS Module |
|
|
14
|
+
| [cli-entry.mjs](cli-entry.md) | JS Module |
|
|
15
15
|
| [ensure-nitra-cursor-dev-dependencies.mjs](ensure-nitra-cursor-dev-dependencies.md) | JS Module |
|
|
16
|
-
| [hook.mjs](hook.md)
|
|
17
|
-
| [post-tool-use-check.mjs](post-tool-use-check.md)
|
|
18
|
-
| [rename-yaml-extensions.mjs](rename-yaml-extensions.md)
|
|
19
|
-
| [skills-cli.mjs](skills-cli.md)
|
|
20
|
-
| [sync-claude-config.mjs](sync-claude-config.md)
|
|
21
|
-
| [sync-setup-bun-deps-action.mjs](sync-setup-bun-deps-action.md)
|
|
22
|
-
| [update-blue-oak.mjs](update-blue-oak.md)
|
|
23
|
-
| [upgrade-nitra-cursor-and-install.mjs](upgrade-nitra-cursor-and-install.md)
|
|
16
|
+
| [hook.mjs](hook.md) | JS Module |
|
|
17
|
+
| [post-tool-use-check.mjs](post-tool-use-check.md) | JS Module |
|
|
18
|
+
| [rename-yaml-extensions.mjs](rename-yaml-extensions.md) | JS Module |
|
|
19
|
+
| [skills-cli.mjs](skills-cli.md) | JS Module |
|
|
20
|
+
| [sync-claude-config.mjs](sync-claude-config.md) | JS Module |
|
|
21
|
+
| [sync-setup-bun-deps-action.mjs](sync-setup-bun-deps-action.md) | JS Module |
|
|
22
|
+
| [update-blue-oak.mjs](update-blue-oak.md) | JS Module |
|
|
23
|
+
| [upgrade-nitra-cursor-and-install.mjs](upgrade-nitra-cursor-and-install.md) | JS Module |
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { existsSync } from 'node:fs'
|
|
18
18
|
import { readdir } from 'node:fs/promises'
|
|
19
19
|
import { join } from 'node:path'
|
|
20
|
+
import { globby } from 'globby'
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* @typedef {object} JsConcern
|
|
@@ -44,20 +45,12 @@ import { join } from 'node:path'
|
|
|
44
45
|
*/
|
|
45
46
|
async function listJsConcerns(jsDir) {
|
|
46
47
|
if (!existsSync(jsDir)) return []
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (entry.name.endsWith('.test.mjs')) continue
|
|
54
|
-
if (entry.name.startsWith('fix-')) continue
|
|
55
|
-
if (entry.name.startsWith('_')) continue
|
|
56
|
-
if (entry.name.startsWith('.')) continue
|
|
57
|
-
const name = entry.name.slice(0, -'.mjs'.length)
|
|
58
|
-
concerns.push({ name })
|
|
59
|
-
}
|
|
60
|
-
return concerns.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
48
|
+
const files = await globby(['*.mjs', '!*.test.mjs', '!fix-*.mjs', '!_*'], {
|
|
49
|
+
cwd: jsDir,
|
|
50
|
+
onlyFiles: true,
|
|
51
|
+
gitignore: false
|
|
52
|
+
})
|
|
53
|
+
return files.map(f => ({ name: f.slice(0, -4) })).toSorted((a, b) => a.name.localeCompare(b.name))
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
/**
|
|
@@ -6,39 +6,39 @@ resource: npm/scripts/lib/
|
|
|
6
6
|
|
|
7
7
|
# npm/scripts/lib
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
11
|
-
| [assert-project-root.mjs](assert-project-root.md)
|
|
12
|
-
| [blue-oak.mjs](blue-oak.md)
|
|
13
|
-
| [changed-files.mjs](changed-files.md)
|
|
14
|
-
| [check-reporter.mjs](check-reporter.md)
|
|
15
|
-
| [diff-added-lines.mjs](diff-added-lines.md)
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| --------------------------------------------------------------------------- | --------- |
|
|
11
|
+
| [assert-project-root.mjs](assert-project-root.md) | JS Module |
|
|
12
|
+
| [blue-oak.mjs](blue-oak.md) | JS Module |
|
|
13
|
+
| [changed-files.mjs](changed-files.md) | JS Module |
|
|
14
|
+
| [check-reporter.mjs](check-reporter.md) | JS Module |
|
|
15
|
+
| [diff-added-lines.mjs](diff-added-lines.md) | JS Module |
|
|
16
16
|
| [discover-check-rules-from-cursor.mjs](discover-check-rules-from-cursor.md) | JS Module |
|
|
17
|
-
| [discover-checkable-rules.mjs](discover-checkable-rules.md)
|
|
18
|
-
| [ensure-tool.mjs](ensure-tool.md)
|
|
19
|
-
| [generated-markdown.mjs](generated-markdown.md)
|
|
20
|
-
| [gha-workflow.mjs](gha-workflow.md)
|
|
21
|
-
| [inline-template-links.mjs](inline-template-links.md)
|
|
22
|
-
| [list-project-rules-mdc.mjs](list-project-rules-mdc.md)
|
|
23
|
-
| [list-rule-ids.mjs](list-rule-ids.md)
|
|
24
|
-
| [load-cursor-config.mjs](load-cursor-config.md)
|
|
25
|
-
| [mirror-parity.mjs](mirror-parity.md)
|
|
26
|
-
| [read-n-cursor-config-lite.mjs](read-n-cursor-config-lite.md)
|
|
27
|
-
| [resolve-target-files.mjs](resolve-target-files.md)
|
|
28
|
-
| [root-notice.mjs](root-notice.md)
|
|
29
|
-
| [rule-meta-helpers.mjs](rule-meta-helpers.md)
|
|
30
|
-
| [rule-meta.mjs](rule-meta.md)
|
|
31
|
-
| [rule-predicates.mjs](rule-predicates.md)
|
|
32
|
-
| [run-conftest-batch.mjs](run-conftest-batch.md)
|
|
33
|
-
| [run-lint-step.mjs](run-lint-step.md)
|
|
34
|
-
| [run-lint.mjs](run-lint.md)
|
|
35
|
-
| [run-rule-cli.mjs](run-rule-cli.md)
|
|
36
|
-
| [run-rule.mjs](run-rule.md)
|
|
37
|
-
| [run-standard-lint.mjs](run-standard-lint.md)
|
|
38
|
-
| [run-standard-rule.mjs](run-standard-rule.md)
|
|
39
|
-
| [skill-meta.mjs](skill-meta.md)
|
|
40
|
-
| [sync-gitignore-worktree.mjs](sync-gitignore-worktree.md)
|
|
41
|
-
| [template.mjs](template.md)
|
|
42
|
-
| [timing-summary.mjs](timing-summary.md)
|
|
43
|
-
| [workspaces.mjs](workspaces.md)
|
|
44
|
-
| [worktree-notice.mjs](worktree-notice.md)
|
|
17
|
+
| [discover-checkable-rules.mjs](discover-checkable-rules.md) | JS Module |
|
|
18
|
+
| [ensure-tool.mjs](ensure-tool.md) | JS Module |
|
|
19
|
+
| [generated-markdown.mjs](generated-markdown.md) | JS Module |
|
|
20
|
+
| [gha-workflow.mjs](gha-workflow.md) | JS Module |
|
|
21
|
+
| [inline-template-links.mjs](inline-template-links.md) | JS Module |
|
|
22
|
+
| [list-project-rules-mdc.mjs](list-project-rules-mdc.md) | JS Module |
|
|
23
|
+
| [list-rule-ids.mjs](list-rule-ids.md) | JS Module |
|
|
24
|
+
| [load-cursor-config.mjs](load-cursor-config.md) | JS Module |
|
|
25
|
+
| [mirror-parity.mjs](mirror-parity.md) | JS Module |
|
|
26
|
+
| [read-n-cursor-config-lite.mjs](read-n-cursor-config-lite.md) | JS Module |
|
|
27
|
+
| [resolve-target-files.mjs](resolve-target-files.md) | JS Module |
|
|
28
|
+
| [root-notice.mjs](root-notice.md) | JS Module |
|
|
29
|
+
| [rule-meta-helpers.mjs](rule-meta-helpers.md) | JS Module |
|
|
30
|
+
| [rule-meta.mjs](rule-meta.md) | JS Module |
|
|
31
|
+
| [rule-predicates.mjs](rule-predicates.md) | JS Module |
|
|
32
|
+
| [run-conftest-batch.mjs](run-conftest-batch.md) | JS Module |
|
|
33
|
+
| [run-lint-step.mjs](run-lint-step.md) | JS Module |
|
|
34
|
+
| [run-lint.mjs](run-lint.md) | JS Module |
|
|
35
|
+
| [run-rule-cli.mjs](run-rule-cli.md) | JS Module |
|
|
36
|
+
| [run-rule.mjs](run-rule.md) | JS Module |
|
|
37
|
+
| [run-standard-lint.mjs](run-standard-lint.md) | JS Module |
|
|
38
|
+
| [run-standard-rule.mjs](run-standard-rule.md) | JS Module |
|
|
39
|
+
| [skill-meta.mjs](skill-meta.md) | JS Module |
|
|
40
|
+
| [sync-gitignore-worktree.mjs](sync-gitignore-worktree.md) | JS Module |
|
|
41
|
+
| [template.mjs](template.md) | JS Module |
|
|
42
|
+
| [timing-summary.mjs](timing-summary.md) | JS Module |
|
|
43
|
+
| [workspaces.mjs](workspaces.md) | JS Module |
|
|
44
|
+
| [worktree-notice.mjs](worktree-notice.md) | JS Module |
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: resolve-target-files.mjs
|
|
4
4
|
resource: npm/scripts/lib/resolve-target-files.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 4d3546e6
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
Цей файл відповідає за перевірку наявності файлів, що відповідають певним правилам, вказаним у файлах `policy/<name>/target.json`. Він створює список файлів для подальшого використання, використовуючи або конкретні відносні шляхи, або обхід каталогу з використанням шаблонів та ігнорування файлів. Це забезпечує узгодженість та контроль над файлами, які потрібно обробити, відповідно до заданих політик.
|
|
@@ -16,7 +16,7 @@ docgen:
|
|
|
16
16
|
4. **Завантаження ігнорування:** Завантажує список абсолютних шляхів, які слід ігнорувати, з конфігурації `.n-cursor.json:ignore`.
|
|
17
17
|
5. **Кешування обходу:** Використовує кеш обходу дерева (Map) для запобігання повторному обходу одного й того ж набору ігнорування. Якщо обхід вже виконано для заданого набору ігнорування, повертає результат з кешу.
|
|
18
18
|
6. **Обхід дерева:** Якщо обхід не кешований або кеш порожній, виконує обхід дерева файлів від заданого кореня, використовуючи `walkDir`. Під час обходу генерує відносні шляхи файлів від кореня.
|
|
19
|
-
7. **Фільтрація за масками:** Для кожного відносного шляху файлу застосовує маски `walkGlob` за допомогою `
|
|
19
|
+
7. **Фільтрація за масками:** Для кожного відносного шляху файлу застосовує маски `walkGlob` за допомогою `ignore`. Фільтрує файли, які відповідають маскам, та виключає ті, що відповідають негативним маскам.
|
|
20
20
|
8. **Збіг з ігноруванням:** Перевіряє, чи файл не входить до списку ігнорування.
|
|
21
21
|
9. **Збіг шляху:** Перетворює відносні шляхи файлів у абсолютні шляхи, додавши корень.
|
|
22
22
|
10. **Повернення результатів:** Повертає масив абсолют
|
|
@@ -29,7 +29,7 @@ resolveTargetFiles — Знаходить файли, що вказані в `ta
|
|
|
29
29
|
|
|
30
30
|
- **Контракт на поліси:** Зчитуються лише файли з репозиторію.
|
|
31
31
|
- **`single`:** Якщо `existsSync`, повертається список файлів, що відповідають `single`. Інакше, повертається порожній список.
|
|
32
|
-
- **`walkGlob`:** Використовується
|
|
32
|
+
- **`walkGlob`:** Використовується ignore для обробки glob-шаблонів відносно шляхів, отриманих з обходу `walkDir` від `root`.
|
|
33
33
|
- **Ігнорування:** Підтримується ігнорування файлів за допомогою `.n-cursor.json:ignore` та кешування шляхів ігнорування у `walkCache`.
|
|
34
34
|
- **Кешування:** Результати обходу кешуються для повторного використання при однаковому наборі ігнорувань.
|
|
35
35
|
- **Path-traversal:** При розв'язанні шляхів у формі `single` виникає помилка.
|
|
@@ -6,42 +6,13 @@
|
|
|
6
6
|
* `t0.mjs` ініціалізує результат через top-level await (один раз при завантаженні модуля).
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync } from 'node:fs'
|
|
9
|
-
import { readdir } from 'node:fs/promises'
|
|
10
9
|
import { join } from 'node:path'
|
|
10
|
+
import { globby } from 'globby'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @typedef {{ id: string, test: (output: string) => boolean, apply: (output: string, cwd: string) => Promise<{ok: boolean, action: string}> | {ok: boolean, action: string} }} T0Pattern
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Повертає абсолютні шляхи до `fix-*.mjs` файлів у директорії (плоско, без рекурсії).
|
|
18
|
-
* @param {string} dir абсолютний шлях до директорії
|
|
19
|
-
* @returns {Promise<string[]>}
|
|
20
|
-
*/
|
|
21
|
-
async function findFixFiles(dir) {
|
|
22
|
-
if (!existsSync(dir)) return []
|
|
23
|
-
const entries = await readdir(dir, { withFileTypes: true })
|
|
24
|
-
return entries
|
|
25
|
-
.filter(e => e.isFile() && e.name.startsWith('fix-') && e.name.endsWith('.mjs'))
|
|
26
|
-
.map(e => join(dir, e.name))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Повертає абсолютні шляхи до `policy/{concern}/fix-*.mjs` у правилі.
|
|
31
|
-
* @param {string} policyDir абсолютний шлях `rules/{rule}/policy/`
|
|
32
|
-
* @returns {Promise<string[]>}
|
|
33
|
-
*/
|
|
34
|
-
async function findPolicyFixFiles(policyDir) {
|
|
35
|
-
if (!existsSync(policyDir)) return []
|
|
36
|
-
const entries = await readdir(policyDir, { withFileTypes: true })
|
|
37
|
-
const paths = []
|
|
38
|
-
for (const entry of entries) {
|
|
39
|
-
if (!entry.isDirectory()) continue
|
|
40
|
-
paths.push(...(await findFixFiles(join(policyDir, entry.name))))
|
|
41
|
-
}
|
|
42
|
-
return paths
|
|
43
|
-
}
|
|
44
|
-
|
|
45
16
|
/**
|
|
46
17
|
* Збирає всі T0-паттерни з `fix-*.mjs` файлів усіх правил у `rulesDir`.
|
|
47
18
|
* @param {string} rulesDir абсолютний шлях до `npm/rules/`
|
|
@@ -49,26 +20,22 @@ async function findPolicyFixFiles(policyDir) {
|
|
|
49
20
|
*/
|
|
50
21
|
export async function discoverT0Patterns(rulesDir) {
|
|
51
22
|
if (!existsSync(rulesDir)) return []
|
|
52
|
-
const ruleEntries = await readdir(rulesDir, { withFileTypes: true })
|
|
53
|
-
/** @type {T0Pattern[]} */
|
|
54
|
-
const allPatterns = []
|
|
55
|
-
|
|
56
|
-
for (const ruleEntry of ruleEntries) {
|
|
57
|
-
if (!ruleEntry.isDirectory() || ruleEntry.name.startsWith('.')) continue
|
|
58
|
-
const ruleDir = join(rulesDir, ruleEntry.name)
|
|
59
23
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
24
|
+
const relPaths = await globby(['*/js/fix-*.mjs', '*/policy/*/fix-*.mjs'], {
|
|
25
|
+
cwd: rulesDir,
|
|
26
|
+
onlyFiles: true,
|
|
27
|
+
gitignore: false
|
|
28
|
+
})
|
|
64
29
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
30
|
+
/** @type {T0Pattern[]} */
|
|
31
|
+
const allPatterns = []
|
|
32
|
+
for (const rel of relPaths) {
|
|
33
|
+
const fixPath = join(rulesDir, rel)
|
|
34
|
+
try {
|
|
35
|
+
const mod = await import(fixPath)
|
|
36
|
+
if (Array.isArray(mod.patterns)) allPatterns.push(...mod.patterns)
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`[discover-t0-patterns] не вдалося імпортувати ${fixPath}: ${err.message}`)
|
|
72
39
|
}
|
|
73
40
|
}
|
|
74
41
|
|
|
@@ -6,16 +6,16 @@ resource: npm/scripts/lib/fix/
|
|
|
6
6
|
|
|
7
7
|
# npm/scripts/lib/fix
|
|
8
8
|
|
|
9
|
-
| Файл
|
|
10
|
-
|
|
11
|
-
| [analyze-escalation.mjs](analyze-escalation.md)
|
|
12
|
-
| [discover-t0-patterns.mjs](discover-t0-patterns.md)
|
|
13
|
-
| [escalation-log.mjs](escalation-log.md)
|
|
14
|
-
| [llm-fix-apply.mjs](llm-fix-apply.md)
|
|
15
|
-
| [llm-lint-fix.mjs](llm-lint-fix.md)
|
|
16
|
-
| [llm-worker.mjs](llm-worker.md)
|
|
17
|
-
| [orchestrator.mjs](orchestrator.md)
|
|
9
|
+
| Файл | Тип |
|
|
10
|
+
| ----------------------------------------------------- | --------- |
|
|
11
|
+
| [analyze-escalation.mjs](analyze-escalation.md) | JS Module |
|
|
12
|
+
| [discover-t0-patterns.mjs](discover-t0-patterns.md) | JS Module |
|
|
13
|
+
| [escalation-log.mjs](escalation-log.md) | JS Module |
|
|
14
|
+
| [llm-fix-apply.mjs](llm-fix-apply.md) | JS Module |
|
|
15
|
+
| [llm-lint-fix.mjs](llm-lint-fix.md) | JS Module |
|
|
16
|
+
| [llm-worker.mjs](llm-worker.md) | JS Module |
|
|
17
|
+
| [orchestrator.mjs](orchestrator.md) | JS Module |
|
|
18
18
|
| [run-conformance-check.mjs](run-conformance-check.md) | JS Module |
|
|
19
|
-
| [t0.mjs](t0.md)
|
|
20
|
-
| [verbose-block.mjs](verbose-block.md)
|
|
21
|
-
| [vscode-ext-add.mjs](vscode-ext-add.md)
|
|
19
|
+
| [t0.mjs](t0.md) | JS Module |
|
|
20
|
+
| [verbose-block.mjs](verbose-block.md) | JS Module |
|
|
21
|
+
| [vscode-ext-add.mjs](vscode-ext-add.md) | JS Module |
|
|
@@ -3,26 +3,31 @@ type: JS Module
|
|
|
3
3
|
title: llm-worker.mjs
|
|
4
4
|
resource: npm/scripts/lib/fix/llm-worker.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 5d019a98
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
score: 100
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
## Огляд
|
|
12
12
|
|
|
13
|
-
Модуль виділяє унікальні відносні шляхи файлів, пов'язаних із
|
|
13
|
+
Модуль виділяє унікальні відносні шляхи файлів, пов'язаних із порушеннями, та виконує LLM-виклик для виправлення одного rule-порушення. Підтримує вибір sub-check `.mdc` за конкретним target-файлом — замість повного `n-{id}.mdc` передає моделі лише релевантну секцію правила (1–2 KB замість 20+ KB).
|
|
14
14
|
|
|
15
15
|
## Поведінка
|
|
16
16
|
|
|
17
|
-
extractFilePaths витягує унікальні відносні шляхи файлів із
|
|
18
|
-
|
|
17
|
+
`extractFilePaths` витягує унікальні відносні шляхи файлів із violation output, пріоритетно розбираючи рядки ❌ (файли що потребують фіксу), потім generic-regex для контексту. Розуміє workspace-prefix `[npm] path/file.ext → npm/path/file.ext`.
|
|
18
|
+
|
|
19
|
+
`selectSubCheckMdc` читає `policy/<concern>/target.json` з каталогу `npm/rules/{ruleId}/` пакету, знаходить concerns із `files.single`, що відповідають failing-файлам із violation output, і повертає конкатенацію відповідних `.mdc`. Повертає `null` якщо правило не має policy-підкаталогу або жоден concern не збігається з ❌-файлами.
|
|
20
|
+
|
|
21
|
+
`runLlmWorker` спочатку викликає `selectSubCheckMdc` — і якщо отримує match, передає моделі лише той sub-check `.mdc`. Якщо match немає — fallback на повний `n-{id}.mdc`. Далі читає файли з violation output, будує prompt, викликає LLM через `callLlmRich` і застосовує зміни.
|
|
19
22
|
|
|
20
23
|
## Публічний API
|
|
21
24
|
|
|
22
|
-
extractFilePaths —
|
|
23
|
-
|
|
25
|
+
`extractFilePaths(output)` — повертає унікальні відносні шляхи файлів з violation output (❌-рядки першими).
|
|
26
|
+
|
|
27
|
+
`runLlmWorker(ruleId, violationOutput, projectRoot, opts)` — виправляє одне порушення правила через LLM. Повертає `{ ok, error?, changes, diagnosis, reasoning, reasoningSource, promptSummary }`. `promptSummary.subCheckMdc: boolean` вказує чи використано точковий sub-check замість повного mdc.
|
|
24
28
|
|
|
25
29
|
## Гарантії поведінки
|
|
26
30
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
31
|
+
- Читає `policy/` безпосередньо з пакету (`import.meta.dirname/../../../rules/`), без pre-generation файлів.
|
|
32
|
+
- Fallback на повний `n-{id}.mdc` при: відсутності policy-підкаталогу, `walkGlob`-таргетах, відсутності ❌-файлів у violation, нульовому match.
|
|
33
|
+
- Перехоплює всі помилки LLM і повертає структурований `{ ok: false, error }`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @see ./docs/llm-worker.md */
|
|
2
2
|
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
5
|
import { env } from 'node:process'
|
|
6
6
|
import { resolveModel } from '../../../lib/models.mjs'
|
|
@@ -20,6 +20,83 @@ const API_KEY_RE = /api key/i
|
|
|
20
20
|
|
|
21
21
|
const FILE_EXTS = 'json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py'
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Каталог `npm/rules/` у пакеті — для вибору sub-check .mdc.
|
|
25
|
+
* Шлях: <package>/npm/scripts/lib/fix/ → ../../.. → npm/ → rules/.
|
|
26
|
+
*/
|
|
27
|
+
const PACKAGE_RULES_DIR = join(import.meta.dirname, '..', '..', '..', 'rules')
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Витягує шляхи файлів лише з рядків ❌ у violation output.
|
|
31
|
+
* Без workspace-розгортання — повертає bare path для звірки з target.json.
|
|
32
|
+
* @param {string} output violation output
|
|
33
|
+
* @returns {string[]} унікальні шляхи з ❌-рядків
|
|
34
|
+
*/
|
|
35
|
+
function extractFailPaths(output) {
|
|
36
|
+
const seen = new Set()
|
|
37
|
+
const add = p => {
|
|
38
|
+
seen.add(p)
|
|
39
|
+
}
|
|
40
|
+
const failSep = `(?::\\d+)?(?::\\s|[\\s—]|$)`
|
|
41
|
+
// ❌ [ws] path/file.ext → strip workspace, зберігаємо bare file
|
|
42
|
+
const failWsRe = new RegExp(`^\\s*❌\\s+\\[[\\w-]+\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
|
|
43
|
+
for (const m of output.matchAll(failWsRe)) add(m[1])
|
|
44
|
+
const failRe = new RegExp(`^\\s*❌\\s+(\\.?[\\w][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
|
|
45
|
+
for (const m of output.matchAll(failRe)) add(m[1])
|
|
46
|
+
return [...seen]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Для правил з `policy/<concern>/` підбирає лише ті concern-.mdc, що відповідають
|
|
51
|
+
* файлам з ❌-рядків violation output. Читає безпосередньо з пакету (`PACKAGE_RULES_DIR`),
|
|
52
|
+
* тому не потребує pre-generation. Fallback — null (→ повний n-{id}.mdc у caller).
|
|
53
|
+
* @param {string} ruleId ID правила
|
|
54
|
+
* @param {string} violationOutput violation output
|
|
55
|
+
* @returns {string|null} конкатенація релевантних .mdc або null
|
|
56
|
+
*/
|
|
57
|
+
function selectSubCheckMdc(ruleId, violationOutput) {
|
|
58
|
+
const policyDir = join(PACKAGE_RULES_DIR, ruleId, 'policy')
|
|
59
|
+
if (!existsSync(policyDir)) return null
|
|
60
|
+
|
|
61
|
+
const failPaths = extractFailPaths(violationOutput)
|
|
62
|
+
if (failPaths.length === 0) return null
|
|
63
|
+
|
|
64
|
+
const matched = []
|
|
65
|
+
let concerns
|
|
66
|
+
try {
|
|
67
|
+
concerns = readdirSync(policyDir, { withFileTypes: true })
|
|
68
|
+
} catch {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const entry of concerns) {
|
|
73
|
+
if (!entry.isDirectory()) continue
|
|
74
|
+
const concernDir = join(policyDir, entry.name)
|
|
75
|
+
const targetPath = join(concernDir, 'target.json')
|
|
76
|
+
if (!existsSync(targetPath)) continue
|
|
77
|
+
|
|
78
|
+
let target
|
|
79
|
+
try {
|
|
80
|
+
target = JSON.parse(readFileSync(targetPath, 'utf8'))
|
|
81
|
+
} catch {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const targetFile = target?.files?.single
|
|
86
|
+
if (!targetFile) continue // walkGlob та інші типи → skip, fallback на main.mdc
|
|
87
|
+
|
|
88
|
+
// Перевіряємо чи хоч один failing path закінчується на targetFile
|
|
89
|
+
const hit = failPaths.some(p => p === targetFile || p.endsWith(`/${targetFile}`))
|
|
90
|
+
if (!hit) continue
|
|
91
|
+
|
|
92
|
+
const mdcEntry = readdirSync(concernDir).find(f => f.endsWith('.mdc'))
|
|
93
|
+
if (!mdcEntry) continue
|
|
94
|
+
matched.push(readFileSync(join(concernDir, mdcEntry), 'utf8').trim())
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return matched.length > 0 ? matched.join('\n\n') : null
|
|
98
|
+
}
|
|
99
|
+
|
|
23
100
|
/**
|
|
24
101
|
* Витягує відносні шляхи файлів із violation output.
|
|
25
102
|
* Розуміє workspace-prefix: `[npm] skills/foo.mjs` → `npm/skills/foo.mjs`.
|
|
@@ -185,9 +262,11 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
|
185
262
|
const timeoutMs = opts.timeoutMs
|
|
186
263
|
const thinkingBudget = opts.thinkingBudget ?? DEFAULT_THINKING_BUDGET
|
|
187
264
|
|
|
188
|
-
// 1. Читаємо rule .mdc
|
|
265
|
+
// 1. Читаємо rule .mdc: спробуємо sub-check mdc для конкретної перевірки,
|
|
266
|
+
// якщо не вдалося — fallback на повний n-{id}.mdc.
|
|
267
|
+
const subMdc = selectSubCheckMdc(ruleId, violationOutput)
|
|
189
268
|
const mdcPath = join(projectRoot, '.cursor', 'rules', `n-${ruleId}.mdc`)
|
|
190
|
-
const ruleMdc = existsSync(mdcPath) ? readFileSync(mdcPath, 'utf8') : '(rule file not found)'
|
|
269
|
+
const ruleMdc = subMdc ?? (existsSync(mdcPath) ? readFileSync(mdcPath, 'utf8') : '(rule file not found)')
|
|
191
270
|
|
|
192
271
|
// 2. Витягуємо файли з violation output і читаємо їх
|
|
193
272
|
const files = readFilesForFix(extractFilePaths(violationOutput), projectRoot)
|
|
@@ -195,6 +274,7 @@ export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
|
|
|
195
274
|
// 3. Будуємо summary промпту (для verbose-блоку) до виклику моделі
|
|
196
275
|
const promptSummary = {
|
|
197
276
|
ruleMdcLen: ruleMdc.length,
|
|
277
|
+
subCheckMdc: !!subMdc,
|
|
198
278
|
violationLen: violationOutput.length,
|
|
199
279
|
filesCount: files.length,
|
|
200
280
|
filesTotalBytes: files.reduce((s, f) => s + f.content.length, 0),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { basename, extname, join } from 'node:path'
|
|
4
|
+
import { globby } from 'globby'
|
|
4
5
|
|
|
5
6
|
const MD_LINK_RE = /\[([^\]]{1,200})\]\((\.\/[^)]{1,500})\)/g
|
|
6
7
|
const TEMPLATE_SEGMENT_RE = /\/templates?\//
|
|
@@ -80,27 +81,18 @@ export async function appendDiscoveredMdcFiles(text, ruleDir) {
|
|
|
80
81
|
|
|
81
82
|
const jsDir = join(ruleDir, 'js')
|
|
82
83
|
if (existsSync(jsDir)) {
|
|
83
|
-
const
|
|
84
|
-
for (const
|
|
85
|
-
|
|
86
|
-
sections.push((await readFile(join(jsDir, e.name), 'utf8')).trim())
|
|
87
|
-
}
|
|
84
|
+
const files = await globby('*.mdc', { cwd: jsDir, onlyFiles: true, gitignore: false })
|
|
85
|
+
for (const f of files.toSorted()) {
|
|
86
|
+
sections.push((await readFile(join(jsDir, f), 'utf8')).trim())
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
const policyDir = join(ruleDir, 'policy')
|
|
92
91
|
if (existsSync(policyDir)) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const concernDir = join(policyDir, concern.name)
|
|
98
|
-
const files = (await readdir(concernDir, { withFileTypes: true }))
|
|
99
|
-
.filter(e => e.isFile() && e.name.endsWith('.mdc'))
|
|
100
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
101
|
-
for (const f of files) {
|
|
102
|
-
sections.push((await readFile(join(concernDir, f.name), 'utf8')).trim())
|
|
103
|
-
}
|
|
92
|
+
// '*/*.mdc' → 'concern/file.mdc'; рядкове сортування дає concern-first, потім file
|
|
93
|
+
const files = await globby('*/*.mdc', { cwd: policyDir, onlyFiles: true, gitignore: false })
|
|
94
|
+
for (const rel of files.toSorted()) {
|
|
95
|
+
sections.push((await readFile(join(policyDir, rel), 'utf8')).trim())
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Винесено зі `bin/n-cursor.js`, щоб ділити між CLI-dispatch і `run-conformance-check` (конформність-детект).
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync } from 'node:fs'
|
|
6
|
-
import { readdir } from 'node:fs/promises'
|
|
7
6
|
import { join } from 'node:path'
|
|
8
7
|
import { cwd as processCwd } from 'node:process'
|
|
8
|
+
import { globby } from 'globby'
|
|
9
9
|
|
|
10
10
|
/** Каталог правил у проєкті-споживачі (відносно кореня). */
|
|
11
11
|
export const CURSOR_RULES_DIR = '.cursor/rules'
|
|
@@ -17,6 +17,6 @@ export const CURSOR_RULES_DIR = '.cursor/rules'
|
|
|
17
17
|
export async function listProjectRulesMdcFiles(cwd = processCwd()) {
|
|
18
18
|
const dir = join(cwd, CURSOR_RULES_DIR)
|
|
19
19
|
if (!existsSync(dir)) return []
|
|
20
|
-
const names = await
|
|
21
|
-
return names.
|
|
20
|
+
const names = await globby('*.mdc', { cwd: dir, onlyFiles: true, gitignore: false })
|
|
21
|
+
return names.toSorted((a, b) => a.localeCompare(b))
|
|
22
22
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Дві форми у `target.json:files`:
|
|
5
5
|
* - `{ "single": "<rel>" }` — конкретний відносний шлях. Якщо `existsSync(root/single)` → `[single]`;
|
|
6
6
|
* інакше `[]` (caller сам вирішує fail vs silent skip за `required`).
|
|
7
|
-
* - `{ "walkGlob": <glob | glob[]> }` —
|
|
7
|
+
* - `{ "walkGlob": <glob | glob[]> }` — `ignore` проти posix-відносних шляхів, отриманих обходом
|
|
8
8
|
* `walkDir` від `root` із загальними skip-ами та `.n-cursor.json:ignore`. Обхід кешований у
|
|
9
9
|
* `walkCache` (Map ключ — підпис ignorePaths) — повторні таргети з тим самим набором ignore
|
|
10
10
|
* перевикористовують список без нового readdir.
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { existsSync } from 'node:fs'
|
|
16
16
|
import { isAbsolute, join, normalize, relative, sep } from 'node:path'
|
|
17
17
|
|
|
18
|
-
import
|
|
18
|
+
import ignore from 'ignore'
|
|
19
19
|
|
|
20
20
|
import { loadCursorIgnorePaths } from './load-cursor-config.mjs'
|
|
21
21
|
import { walkDir } from '../utils/walkDir.mjs'
|
|
@@ -96,14 +96,8 @@ export async function resolveTargetFiles(filesSpec, root, walkCache) {
|
|
|
96
96
|
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
97
97
|
const all = await getAllFilesCached(root, ignorePaths, walkCache)
|
|
98
98
|
const globs = Array.isArray(filesSpec.walkGlob) ? filesSpec.walkGlob : [filesSpec.walkGlob]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// позитиви join-имо через picomatch(...), негативні фільтруємо окремим isExcluded.
|
|
102
|
-
const positives = globs.filter(g => !g.startsWith('!'))
|
|
103
|
-
const negatives = globs.filter(g => g.startsWith('!')).map(g => g.slice(1))
|
|
104
|
-
const isMatch = positives.length > 0 ? picomatch(positives, { dot: false }) : () => false
|
|
105
|
-
const isExcluded = negatives.length > 0 ? picomatch(negatives, { dot: false }) : () => false
|
|
106
|
-
return all.filter(rel => isMatch(rel) && !isExcluded(rel)).map(rel => join(root, rel))
|
|
99
|
+
const ig = ignore().add(globs)
|
|
100
|
+
return all.filter(rel => ig.ignores(rel)).map(rel => join(root, rel))
|
|
107
101
|
}
|
|
108
102
|
throw new Error(`target.json: files має містити single або walkGlob (отримано: ${JSON.stringify(filesSpec)})`)
|
|
109
103
|
}
|
|
@@ -1,84 +1,44 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Обходить дерево від заданого кореня; для кожного звичайного файлу викликає переданий callback.
|
|
5
|
-
* Каталоги node_modules, .git, dist, coverage, .turbo, .next не заходяться.
|
|
6
|
-
* Додатково можна передати `ignorePaths` — повні шляхи каталогів (абсолютні posix), які слід
|
|
7
|
-
* пропускати разом з усім вмістом (поле `ignore` у `.n-cursor.json`). Якщо readdir для каталогу
|
|
8
|
-
* не вдається — тихо виходить без throw.
|
|
9
|
-
*/
|
|
10
|
-
import { readdir } from 'node:fs/promises'
|
|
11
|
-
import { isAbsolute, join, resolve, sep } from 'node:path'
|
|
1
|
+
/** @see ./docs/walkDir.md */
|
|
2
|
+
import { join, relative, resolve, sep } from 'node:path'
|
|
3
|
+
import { globby } from 'globby'
|
|
12
4
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
* @returns {string} абсолютний posix-шлях
|
|
17
|
-
*/
|
|
18
|
-
function toAbsPosix(p) {
|
|
19
|
-
const abs = isAbsolute(p) ? p : resolve(p)
|
|
20
|
-
let posix = abs.split(sep).join('/')
|
|
21
|
-
while (posix.endsWith('/')) posix = posix.slice(0, -1)
|
|
22
|
-
return posix
|
|
23
|
-
}
|
|
5
|
+
// .git ніколи не потрапляє в .gitignore — пропускаємо завжди.
|
|
6
|
+
// node_modules — safety net: проєкт може не мати .gitignore або запускатись поза git-репо.
|
|
7
|
+
const ALWAYS_IGNORE = ['.git/**', 'node_modules/**']
|
|
24
8
|
|
|
25
9
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @param {string}
|
|
29
|
-
* @param {string[]}
|
|
30
|
-
* @returns {
|
|
31
|
-
*/
|
|
32
|
-
function isIgnoredDir(dirAbsPosix, ignorePosix) {
|
|
33
|
-
for (const ig of ignorePosix) {
|
|
34
|
-
if (dirAbsPosix === ig) return true
|
|
35
|
-
if (dirAbsPosix.startsWith(`${ig}/`)) return true
|
|
36
|
-
}
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Рекурсивно обходить каталог, пропускає типові артефакти збірки/залежностей та `ignorePaths`.
|
|
42
|
-
* @param {string} dir абсолютний шлях
|
|
43
|
-
* @param {(filePath: string) => void} onFile виклик для кожного файлу
|
|
44
|
-
* @param {string[]} [ignorePaths] шляхи каталогів (відносні від cwd або абсолютні), що повністю виключаються з обходу
|
|
45
|
-
* @returns {Promise<void>} визначається по завершенню обходу
|
|
10
|
+
* Рекурсивно обходить каталог, поважаючи .gitignore (включно з вкладеними).
|
|
11
|
+
* @param {string} dir абсолютний або відносний шлях до кореня обходу
|
|
12
|
+
* @param {(filePath: string) => void} onFile колбек для кожного файлу (абсолютний шлях)
|
|
13
|
+
* @param {string[]} [ignorePaths] додаткові шляхи для пропуску (абсолютні або відносні від cwd)
|
|
14
|
+
* @returns {Promise<void>}
|
|
46
15
|
*/
|
|
47
16
|
export async function walkDir(dir, onFile, ignorePaths = []) {
|
|
48
|
-
const
|
|
49
|
-
await walkDirInner(dir, onFile, ignorePosix)
|
|
50
|
-
}
|
|
17
|
+
const absDir = resolve(dir)
|
|
51
18
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let
|
|
19
|
+
const extraIgnore = ignorePaths
|
|
20
|
+
.map(p => {
|
|
21
|
+
const abs = resolve(p.replace(/\/+$/, ''))
|
|
22
|
+
const rel = relative(absDir, abs).split(sep).join('/')
|
|
23
|
+
if (rel.startsWith('..') || rel === '') return null
|
|
24
|
+
return `${rel}/**`
|
|
25
|
+
})
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
|
|
28
|
+
let files
|
|
62
29
|
try {
|
|
63
|
-
|
|
30
|
+
files = await globby('**/*', {
|
|
31
|
+
cwd: absDir,
|
|
32
|
+
gitignore: true,
|
|
33
|
+
dot: true,
|
|
34
|
+
onlyFiles: true,
|
|
35
|
+
ignore: [...ALWAYS_IGNORE, ...extraIgnore]
|
|
36
|
+
})
|
|
64
37
|
} catch {
|
|
65
38
|
return
|
|
66
39
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const skipDir =
|
|
71
|
-
e.name === 'node_modules' ||
|
|
72
|
-
e.name === '.git' ||
|
|
73
|
-
e.name === 'dist' ||
|
|
74
|
-
e.name === 'coverage' ||
|
|
75
|
-
e.name === '.turbo' ||
|
|
76
|
-
e.name === '.next'
|
|
77
|
-
if (skipDir) continue
|
|
78
|
-
if (ignorePosix.length > 0 && isIgnoredDir(toAbsPosix(p), ignorePosix)) continue
|
|
79
|
-
await walkDirInner(p, onFile, ignorePosix)
|
|
80
|
-
} else if (e.isFile()) {
|
|
81
|
-
onFile(p)
|
|
82
|
-
}
|
|
40
|
+
|
|
41
|
+
for (const rel of files) {
|
|
42
|
+
onFile(join(absDir, rel))
|
|
83
43
|
}
|
|
84
44
|
}
|
|
@@ -44,6 +44,7 @@ node -e "const p=JSON.parse(require('fs').readFileSync('package.json','utf8'));
|
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
Для кожного воркспейсу `<ws>`:
|
|
47
|
+
|
|
47
48
|
- `relRoot` = `<ws>` (напр. `npm`, `demo`)
|
|
48
49
|
- `docPath` = `<ws>/docs/ARCHITECTURE.md`
|
|
49
50
|
- `members` — кодові файли (`.mjs .ts .vue .py`, крім тестів) у `<ws>/`
|
|
@@ -11,12 +11,15 @@ version: '1.0'
|
|
|
11
11
|
|
|
12
12
|
## Формат Telegram
|
|
13
13
|
|
|
14
|
-
Telegram підтримує
|
|
14
|
+
Telegram підтримує Markdown-форматування безпосередньо в клієнті:
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
- `
|
|
16
|
+
- `**bold**` — жирний (заголовки, мітки секцій)
|
|
17
|
+
- `_italic_` — курсив (акценти)
|
|
18
|
+
- `` `inline code` `` — inline-код, команди, назви файлів/пакетів
|
|
19
|
+
- ` ```block``` ` (потрійні backticks) — блок коду, коли є реальний код
|
|
20
|
+
- `~~strikethrough~~` — закреслення (опціонально)
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
Секції (Проблема:, Рішення: тощо) виділяй `**жирним**`. Технічні терміни (назви файлів, пакетів, команди) — в `` `inline code` ``.
|
|
20
23
|
|
|
21
24
|
## Workflow
|
|
22
25
|
|
|
@@ -28,23 +31,23 @@ Telegram підтримує моноширний шрифт через markdown-
|
|
|
28
31
|
|
|
29
32
|
## Шаблон посту
|
|
30
33
|
|
|
31
|
-
```
|
|
34
|
+
```
|
|
32
35
|
#тег
|
|
33
36
|
|
|
34
|
-
📌
|
|
37
|
+
📌 **<Короткий заголовок>**
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
**Проблема:**
|
|
37
40
|
<1-3 речення — що було не так / яке завдання>
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
**Рішення:**
|
|
40
43
|
<2-5 речень — що зробили, який підхід>
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
**Деталі:**
|
|
43
46
|
• <ключовий момент 1>
|
|
44
47
|
• <ключовий момент 2>
|
|
45
48
|
• <ключовий момент 3>
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
**Результат:**
|
|
48
51
|
<1-2 речення — що отримали, яка вигода>
|
|
49
52
|
```
|
|
50
53
|
|
|
@@ -64,32 +67,27 @@ Telegram підтримує моноширний шрифт через markdown-
|
|
|
64
67
|
- Тег: рівно 1 хештег першим рядком поста (#devops, #frontend, #bugfix, #refactoring, #CI, #performance тощо)
|
|
65
68
|
- Emoji: лише структурні (📌 для заголовка, • для списку), не перевантажувати
|
|
66
69
|
- Цільова аудиторія: розробники та менеджери — тому пояснюй простою мовою, без надмірного жаргону
|
|
67
|
-
-
|
|
70
|
+
- Назви файлів, пакетів, команди — у `` `inline code` ``
|
|
71
|
+
- Блок коду (потрійні backticks) — лише якщо є реальний код/конфіг, не для всього тексту
|
|
68
72
|
|
|
69
73
|
## Приклад
|
|
70
74
|
|
|
71
|
-
````
|
|
72
75
|
```
|
|
73
76
|
#dev
|
|
74
77
|
|
|
75
|
-
📌
|
|
78
|
+
📌 **Міграція з Prettier на oxfmt**
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
Prettier повільно форматував великі файли і
|
|
79
|
-
конфліктував з ESLint при роботі з Vue SFC.
|
|
80
|
+
**Проблема:**
|
|
81
|
+
Prettier повільно форматував великі файли і конфліктував з ESLint при роботі з Vue SFC.
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
Замінили Prettier на oxfmt — нативний
|
|
83
|
-
форматер від OXC. Оновили VS Code settings,
|
|
84
|
-
видалили prettier-конфіги, додали .oxfmtrc.json.
|
|
83
|
+
**Рішення:**
|
|
84
|
+
Замінили Prettier на `oxfmt` — нативний форматер від OXC. Оновили VS Code settings, видалили prettier-конфіги, додали `.oxfmtrc.json`.
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
• oxfmt працює в 10-50x швидше за Prettier
|
|
88
|
-
• Єдиний конфіг
|
|
89
|
-
• CI перевіряє форматування через lint-js
|
|
86
|
+
**Деталі:**
|
|
87
|
+
• `oxfmt` працює в 10-50x швидше за Prettier
|
|
88
|
+
• Єдиний конфіг `.oxfmtrc.json` у корені
|
|
89
|
+
• CI перевіряє форматування через `lint-js`
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
Форматування працює миттєво при збереженні,
|
|
93
|
-
зникли конфлікти між форматером і лінтером.
|
|
91
|
+
**Результат:**
|
|
92
|
+
Форматування працює миттєво при збереженні, зникли конфлікти між форматером і лінтером.
|
|
94
93
|
```
|
|
95
|
-
````
|