@nitra/cursor 12.12.0 → 12.13.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 +6 -0
- package/package.json +2 -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/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/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/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/cursor",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.13.0",
|
|
4
4
|
"description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@7n/mt": "^0.5.1",
|
|
57
|
+
"globby": "^16.0.0",
|
|
57
58
|
"oxc-parser": "^0.137.0",
|
|
58
59
|
"picomatch": "^4.0.4",
|
|
59
60
|
"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 |
|
|
@@ -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 |
|
|
@@ -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
|
}
|
|
@@ -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
|
-
````
|