@nitra/cursor 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-template/settings.template.json +22 -0
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +15 -9
- package/CHANGELOG.md +12 -1
- package/bin/n-cursor.js +73 -16
- package/docs/stryker.config.md +6 -0
- package/docs/vitest.config.md +6 -0
- package/lib/docs/llm.md +29 -0
- package/lib/docs/omlx.md +32 -0
- package/lib/llm.mjs +137 -0
- package/lib/omlx.mjs +49 -4
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +6 -0
- package/rules/abie/js/docs/applies.md +6 -0
- package/rules/abie/js/docs/env_dns.md +25 -22
- package/rules/abie/js/docs/firebase_hosting.md +6 -0
- package/rules/abie/js/docs/hc_pairing.md +21 -25
- package/rules/abie/js/docs/ua_http_route.md +27 -19
- package/rules/abie/js/docs/ua_node_selector.md +24 -19
- package/rules/abie/lib/docs/enabled.md +13 -7
- package/rules/abie/lib/docs/env-dns.md +9 -3
- package/rules/abie/lib/docs/hc-yaml.md +6 -0
- package/rules/abie/lib/docs/http-route.md +6 -0
- package/rules/abie/lib/docs/k8s-tree.md +6 -0
- package/rules/abie/lib/docs/kustomization-patches.md +6 -0
- package/rules/abie/lib/docs/overlay-paths.md +6 -0
- package/rules/abie/lib/docs/yaml.md +6 -0
- package/rules/adr/docs/fix.md +6 -0
- package/rules/adr/js/docs/hooks.md +29 -244
- package/rules/bun/docs/fix.md +6 -0
- package/rules/bun/js/docs/layout.md +37 -375
- package/rules/capacitor/docs/fix.md +22 -108
- package/rules/capacitor/js/docs/platforms.md +62 -268
- package/rules/changelog/docs/fix.md +6 -0
- package/rules/changelog/lib/docs/package-manifest.md +6 -0
- package/rules/ci4/docs/fix.md +23 -165
- package/rules/ci4/js/docs/marksman_config.md +9 -1
- package/rules/docker/docs/fix.md +6 -0
- package/rules/docker/js/docs/lint.md +55 -239
- package/rules/docker/lib/docs/docker-hadolint.md +6 -0
- package/rules/docker/lib/docs/docker-mirror.md +6 -0
- package/rules/docker/lib/docs/docker-native-addon.md +6 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +6 -0
- package/rules/docker/lint/docs/lint.md +9 -1
- package/rules/efes/docs/fix.md +6 -0
- package/rules/ga/lint/docs/lint.md +6 -0
- package/rules/graphql/docs/fix.md +6 -0
- package/rules/graphql/lib/docs/graphql-gql-scan.md +6 -0
- package/rules/image-avif/docs/fix.md +6 -0
- package/rules/image-avif/js/docs/avif_generation.md +6 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +9 -3
- package/rules/js-bun-redis/lib/docs/redis-imports.md +6 -0
- package/rules/js-lint/js/docs/utils_imports.md +6 -0
- package/rules/js-lint-ci/docs/fix.md +7 -1
- package/rules/js-mssql/docs/fix.md +6 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +6 -0
- package/rules/js-run/docs/fix.md +6 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +6 -0
- package/rules/js-run/lib/docs/check-env-scan.md +6 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +6 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +6 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +6 -0
- package/rules/js-run/lib/docs/temporal-scan.md +6 -0
- package/rules/k8s/docs/fix.md +6 -0
- package/rules/k8s/lint/docs/lint.md +6 -0
- package/rules/nginx-default-tpl/docs/fix.md +6 -0
- package/rules/npm-module/js/docs/header_doc_pointer.md +7 -0
- package/rules/npm-module/js/header_doc_pointer.mjs +2 -8
- package/rules/php/docs/fix.md +6 -0
- package/rules/php/lint/docs/lint.md +6 -0
- package/rules/python/docs/fix.md +6 -0
- package/rules/python/lint/docs/lint.md +6 -0
- package/rules/rego/lint/docs/lint.md +6 -0
- package/rules/release/docs/change.md +6 -0
- package/rules/release/docs/fix.md +6 -0
- package/rules/release/docs/release.md +6 -0
- package/rules/release/lib/docs/aggregate.md +6 -0
- package/rules/release/lib/docs/change-file.md +6 -0
- package/rules/release/lib/docs/fallback.md +6 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +6 -0
- package/rules/security/docs/fix.md +7 -1
- package/rules/security/js/docs/lint.md +6 -0
- package/rules/style-lint/docs/fix.md +6 -0
- package/rules/tauri/docs/fix.md +6 -0
- package/rules/test/docs/fix.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +6 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +6 -0
- package/rules/text/docs/fix.md +6 -0
- package/rules/text/lint/docs/lint.md +6 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +6 -0
- package/rules/text/lint/docs/run-shellcheck.md +6 -0
- package/rules/text/lint/docs/run-v8r.md +6 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +6 -0
- package/scripts/coverage-classify/cache.mjs +1 -1
- package/scripts/coverage-classify/docs/apply.md +6 -0
- package/scripts/coverage-classify/docs/cache.md +6 -0
- package/scripts/coverage-classify/docs/prompt.md +6 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +6 -0
- package/scripts/coverage-classify/prompt.mjs +1 -1
- package/scripts/coverage-fix-extract.mjs +1 -1
- package/scripts/coverage-fix.mjs +2 -1
- package/scripts/docs/auto-skills.md +6 -0
- package/scripts/docs/build-agents-commands.md +7 -1
- package/scripts/docs/cli-entry.md +6 -0
- package/scripts/docs/coverage-fix-extract.md +6 -0
- package/scripts/docs/coverage-fix.md +6 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +6 -0
- package/scripts/docs/lint-cli.md +6 -0
- package/scripts/docs/post-tool-use-fix.md +6 -0
- package/scripts/docs/rename-yaml-extensions.md +6 -0
- package/scripts/docs/skills-cli.md +6 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +6 -0
- package/scripts/docs/upgrade-nitra-cursor-and-install.md +6 -0
- package/scripts/docs/worktree-cli.md +6 -0
- package/scripts/lib/docs/assert-project-root.md +6 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +6 -0
- package/scripts/lib/docs/check-reporter.md +6 -0
- package/scripts/lib/docs/diff-added-lines.md +6 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +6 -0
- package/scripts/lib/docs/discover-checkable-rules.md +6 -0
- package/scripts/lib/docs/ensure-tool.md +6 -0
- package/scripts/lib/docs/generated-markdown.md +6 -0
- package/scripts/lib/docs/gha-workflow.md +6 -0
- package/scripts/lib/docs/inline-template-links.md +6 -0
- package/scripts/lib/docs/list-rule-ids.md +6 -0
- package/scripts/lib/docs/load-cursor-config.md +6 -0
- package/scripts/lib/docs/mirror-parity.md +6 -0
- package/scripts/lib/docs/read-n-cursor-config-lite.md +6 -0
- package/scripts/lib/docs/resolve-target-files.md +6 -0
- package/scripts/lib/docs/root-notice.md +6 -0
- package/scripts/lib/docs/rule-meta-helpers.md +6 -0
- package/scripts/lib/docs/rule-meta.md +6 -0
- package/scripts/lib/docs/run-conftest-batch.md +6 -0
- package/scripts/lib/docs/run-lint-step.md +6 -0
- package/scripts/lib/docs/run-rule-cli.md +6 -0
- package/scripts/lib/docs/run-rule.md +6 -0
- package/scripts/lib/docs/run-standard-lint.md +6 -0
- package/scripts/lib/docs/run-standard-rule.md +6 -0
- package/scripts/lib/docs/skill-meta.md +6 -0
- package/scripts/lib/docs/template.md +6 -0
- package/scripts/lib/docs/timing-summary.md +6 -0
- package/scripts/lib/docs/workspaces.md +6 -0
- package/scripts/lib/docs/worktree-notice.md +6 -0
- package/scripts/lib/docs/worktree.md +6 -0
- package/scripts/lib/mirror-parity.mjs +1 -1
- package/scripts/lib/root-notice.mjs +1 -1
- package/scripts/lib/worktree-notice.mjs +5 -5
- package/scripts/lib/worktree.mjs +1 -1
- package/scripts/sync-claude-config.mjs +3 -0
- package/scripts/utils/docs/ast-scan-utils.md +6 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +6 -0
- package/scripts/utils/docs/find-package-json-paths.md +6 -0
- package/scripts/utils/docs/lock-cache-dir.md +6 -0
- package/scripts/utils/docs/pass.md +6 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +6 -0
- package/scripts/utils/docs/resolve-cmd.md +6 -0
- package/scripts/utils/docs/resolve-js-root.md +6 -0
- package/scripts/utils/docs/test-helpers.md +6 -0
- package/scripts/utils/docs/walk-cache.md +6 -0
- package/scripts/utils/docs/walkDir.md +6 -0
- package/scripts/utils/docs/worktree-fingerprint.md +6 -0
- package/scripts/utils/resolve-js-root.mjs +1 -1
- package/skills/doc-aggregate/SKILL.md +129 -0
- package/skills/doc-aggregate/js/docgen-ignore.mjs +9 -0
- package/skills/{docgen → doc-aggregate}/js/docgen-scan.mjs +22 -67
- package/skills/doc-aggregate/js/docs/docgen-ignore.md +21 -0
- package/skills/doc-files/SKILL.md +100 -0
- package/skills/doc-files/js/docgen-crc.mjs +164 -0
- package/skills/{docgen → doc-files}/js/docgen-extract-anchors.mjs +20 -11
- package/skills/{docgen → doc-files}/js/docgen-extract.mjs +15 -9
- package/skills/doc-files/js/docgen-files-batch.mjs +181 -0
- package/skills/doc-files/js/docgen-gen.mjs +291 -0
- package/skills/{docgen → doc-files}/js/docgen-prompts.mjs +43 -40
- package/skills/doc-files/js/docgen-scan.mjs +298 -0
- package/skills/doc-files/js/docs/docgen-crc.md +32 -0
- package/skills/doc-files/js/docs/docgen-extract-anchors.md +27 -0
- package/skills/doc-files/js/docs/docgen-extract.md +29 -0
- package/skills/doc-files/js/docs/docgen-files-batch.md +25 -0
- package/skills/doc-files/js/docs/docgen-gen.md +30 -0
- package/skills/doc-files/js/docs/docgen-prompts.md +32 -0
- package/skills/doc-files/js/docs/docgen-scan.md +25 -0
- package/skills/doc-files/meta.json +1 -0
- package/skills/fix/js/docs/llm-worker.md +6 -0
- package/skills/fix/js/docs/orchestrator.md +6 -0
- package/skills/fix/js/llm-worker.mjs +3 -3
- package/skills/fix/js/orchestrator.mjs +1 -1
- package/skills/start-check/js/check.mjs +5 -3
- package/skills/start-check/js/docs/check.md +6 -0
- package/skills/docgen/SKILL.md +0 -224
- package/skills/docgen/bench/etalon/firebase_hosting.md +0 -19
- package/skills/docgen/bench/etalon/k8s-tree.md +0 -24
- package/skills/docgen/bench/etalon/overlay-paths.md +0 -24
- package/skills/docgen/js/docgen-batch-omlx.mjs +0 -82
- package/skills/docgen/js/docgen-batch.mjs +0 -95
- package/skills/docgen/js/docgen-compare-pi-vs-direct.mjs +0 -95
- package/skills/docgen/js/docgen-gen.mjs +0 -306
- package/skills/docgen/js/docs/docgen-extract.md +0 -28
- package/skills/docgen/js/docs/docgen-gen.md +0 -41
- package/skills/docgen/js/docs/docgen-ignore.md +0 -24
- package/skills/docgen/js/docs/docgen-prompts.md +0 -24
- package/skills/docgen/js/docs/docgen-scan.md +0 -48
- /package/skills/{docgen → doc-aggregate}/meta.json +0 -0
- /package/skills/{docgen → doc-files}/js/docgen-ignore.mjs +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export спільного списку ignore-глобів зі скіла doc-files.
|
|
3
|
+
*
|
|
4
|
+
* Канонічне джерело — `npm/skills/doc-files/js/docgen-ignore.mjs`: обидва скіли
|
|
5
|
+
* (file-level доки і агрегати) мусять бачити однакове дерево кодових файлів,
|
|
6
|
+
* інакше агрегат посилатиметься на файли без док (або навпаки). Залежність
|
|
7
|
+
* спрямована doc-aggregate → doc-files за ADR про розбиття docgen.
|
|
8
|
+
*/
|
|
9
|
+
export * from '../../doc-files/js/docgen-ignore.mjs'
|
|
@@ -13,9 +13,7 @@ const SOURCE_EXTENSIONS = new Set(['.js', '.mjs', '.ts', '.vue', '.py'])
|
|
|
13
13
|
const TEST_FILE_RE = /\.(?:test|spec)\.[^.]+$/u
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Чи корінь має system-wide docs layout.
|
|
17
|
-
* Такий корінь зарезервований під репозиторні docs/adr, docs/explanation тощо,
|
|
18
|
-
* тому file-level docs у нього не пишемо.
|
|
16
|
+
* Чи корінь має system-wide docs layout (зарезервований під repo docs/adr тощо).
|
|
19
17
|
* @param {string} root абсолютний корінь обходу
|
|
20
18
|
* @returns {boolean} true — корінь system-wide docs
|
|
21
19
|
*/
|
|
@@ -26,7 +24,7 @@ function isSystemWideDocsRoot(root) {
|
|
|
26
24
|
/**
|
|
27
25
|
* Чи є файл кодовим джерелом для документування.
|
|
28
26
|
* @param {string} fileName базове ім'я файлу
|
|
29
|
-
* @returns {boolean} true —
|
|
27
|
+
* @returns {boolean} true — документуємо
|
|
30
28
|
*/
|
|
31
29
|
export function isSourceFile(fileName) {
|
|
32
30
|
if (fileName.endsWith('.d.ts')) return false
|
|
@@ -35,30 +33,14 @@ export function isSourceFile(fileName) {
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
|
-
*
|
|
39
|
-
* Якщо `sourcePath` відносний, `docPath` теж відносний; якщо абсолютний — абсолютний.
|
|
40
|
-
* @param {string} sourcePath шлях до джерела (відносний або абсолютний)
|
|
41
|
-
* @returns {string} шлях до `<dir>/docs/<stem>.md` у тому ж просторі шляхів
|
|
42
|
-
*/
|
|
43
|
-
export function docPathForSource(sourcePath) {
|
|
44
|
-
const dir = path.dirname(sourcePath)
|
|
45
|
-
const stem = path.basename(sourcePath, path.extname(sourcePath))
|
|
46
|
-
return path.join(dir, 'docs', `${stem}.md`)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Рекурсивно обходить дерево від `root`, повертає кодові файли для документування.
|
|
51
|
-
* Синхронний `readdirSync` — детермінований порядок і простий рекурсивний обхід без
|
|
52
|
-
* гонок; обсяг дерева проєкту це дозволяє.
|
|
36
|
+
* Рекурсивно збирає кодові файли проєкту (posix-шляхи від кореня).
|
|
53
37
|
* @param {string} root абсолютний корінь обходу
|
|
54
|
-
* @returns {
|
|
38
|
+
* @returns {string[]} sourcePath-и
|
|
55
39
|
*/
|
|
56
|
-
export function
|
|
40
|
+
export function scanSourceFiles(root) {
|
|
57
41
|
const results = []
|
|
58
42
|
|
|
59
|
-
/**
|
|
60
|
-
* @param {string} dir поточний каталог обходу
|
|
61
|
-
*/
|
|
43
|
+
/** @param {string} dir поточний каталог обходу */
|
|
62
44
|
function walk(dir) {
|
|
63
45
|
let entries
|
|
64
46
|
try {
|
|
@@ -76,12 +58,7 @@ export function scanForDocgen(root) {
|
|
|
76
58
|
if (isSystemWideDocsRoot(root) && path.dirname(relPath) === '.') continue
|
|
77
59
|
const sourcePath = relPath.split(path.sep).join('/')
|
|
78
60
|
if (isDocgenIgnored(sourcePath)) continue
|
|
79
|
-
|
|
80
|
-
results.push({
|
|
81
|
-
sourcePath,
|
|
82
|
-
docPath,
|
|
83
|
-
exists: existsSync(path.join(root, docPath))
|
|
84
|
-
})
|
|
61
|
+
results.push(sourcePath)
|
|
85
62
|
}
|
|
86
63
|
}
|
|
87
64
|
}
|
|
@@ -98,7 +75,6 @@ export function scanForDocgen(root) {
|
|
|
98
75
|
*/
|
|
99
76
|
export function slugForModule(root, moduleRoot) {
|
|
100
77
|
const rel = path.relative(root, moduleRoot)
|
|
101
|
-
// корінь репо: фіксований sentinel 'root'
|
|
102
78
|
if (rel === '') return 'root'
|
|
103
79
|
return rel
|
|
104
80
|
.split(path.sep)
|
|
@@ -108,7 +84,6 @@ export function slugForModule(root, moduleRoot) {
|
|
|
108
84
|
|
|
109
85
|
/**
|
|
110
86
|
* Знаходить корені модулів — теки з `package.json` (корінь завжди модуль).
|
|
111
|
-
* Ті ж ignore-glob правила, тож `package.json` у службових деревах не враховується.
|
|
112
87
|
* @param {string} root абсолютний корінь обходу
|
|
113
88
|
* @returns {string[]} абсолютні шляхи коренів модулів
|
|
114
89
|
*/
|
|
@@ -159,17 +134,17 @@ export function nearestModuleRoot(filePath, moduleRoots) {
|
|
|
159
134
|
* Лістить логічні модулі проєкту з членами-файлами і docPath module-summary.
|
|
160
135
|
* Модулі без кодових файлів пропускаються.
|
|
161
136
|
* @param {string} root абсолютний корінь обходу
|
|
162
|
-
* @returns {Array<{moduleRoot:string, relRoot:string, slug:string, docPath:string, members:string[], exists:boolean}>} модулі (members — sourcePath
|
|
137
|
+
* @returns {Array<{moduleRoot:string, relRoot:string, slug:string, docPath:string, members:string[], exists:boolean}>} модулі (members — sourcePath-и від root)
|
|
163
138
|
*/
|
|
164
139
|
export function scanForModules(root) {
|
|
165
|
-
const files =
|
|
140
|
+
const files = scanSourceFiles(root)
|
|
166
141
|
const moduleRoots = findModuleRoots(root)
|
|
167
142
|
const byRoot = new Map()
|
|
168
|
-
for (const
|
|
169
|
-
const moduleRoot = nearestModuleRoot(path.join(root,
|
|
143
|
+
for (const sourcePath of files) {
|
|
144
|
+
const moduleRoot = nearestModuleRoot(path.join(root, sourcePath), moduleRoots)
|
|
170
145
|
if (moduleRoot === null) continue
|
|
171
146
|
if (!byRoot.has(moduleRoot)) byRoot.set(moduleRoot, [])
|
|
172
|
-
byRoot.get(moduleRoot).push(
|
|
147
|
+
byRoot.get(moduleRoot).push(sourcePath)
|
|
173
148
|
}
|
|
174
149
|
|
|
175
150
|
const results = []
|
|
@@ -190,7 +165,7 @@ export function scanForModules(root) {
|
|
|
190
165
|
}
|
|
191
166
|
|
|
192
167
|
/**
|
|
193
|
-
* Парсить `--root <dir
|
|
168
|
+
* Парсить `--root <dir>`; default — cwd.
|
|
194
169
|
* @param {string[]} argv аргументи після підкоманди
|
|
195
170
|
* @returns {string} абсолютний корінь
|
|
196
171
|
*/
|
|
@@ -200,42 +175,22 @@ export function resolveRoot(argv) {
|
|
|
200
175
|
}
|
|
201
176
|
|
|
202
177
|
/**
|
|
203
|
-
*
|
|
204
|
-
* @param {string[]} argv аргументи після назви субкоманди
|
|
205
|
-
* @returns {
|
|
206
|
-
*/
|
|
207
|
-
export async function runDocgenScanCli(argv) {
|
|
208
|
-
const root = resolveRoot(argv)
|
|
209
|
-
|
|
210
|
-
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
211
|
-
console.error(`docgen scan: корінь не існує або не є директорією: ${root}`)
|
|
212
|
-
return 1
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const items = await scanForDocgen(root)
|
|
216
|
-
console.log(JSON.stringify(items, null, 2))
|
|
217
|
-
return 0
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Парсить `--root`, сканує модулі і друкує JSON-масив у stdout.
|
|
222
|
-
* @param {string[]} argv аргументи після назви субкоманди (наприклад ['--root', '<dir>'])
|
|
223
|
-
* @returns {Promise<number>} exit-код: 0 — успіх, 1 — корінь не існує
|
|
178
|
+
* `doc-aggregate modules` — сканує модулі і друкує JSON-масив у stdout.
|
|
179
|
+
* @param {string[]} argv аргументи після назви субкоманди
|
|
180
|
+
* @returns {number} exit-код: 0 — успіх, 1 — корінь не існує
|
|
224
181
|
*/
|
|
225
|
-
export
|
|
182
|
+
export function runDocAggregateModulesCli(argv) {
|
|
226
183
|
const root = resolveRoot(argv)
|
|
227
|
-
|
|
228
184
|
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
229
|
-
console.error(`
|
|
185
|
+
console.error(`doc-aggregate modules: корінь не існує або не є директорією: ${root}`)
|
|
230
186
|
return 1
|
|
231
187
|
}
|
|
232
|
-
|
|
233
|
-
const items = await scanForModules(root)
|
|
234
|
-
console.log(JSON.stringify(items, null, 2))
|
|
188
|
+
console.log(JSON.stringify(scanForModules(root), null, 2))
|
|
235
189
|
return 0
|
|
236
190
|
}
|
|
237
191
|
|
|
238
192
|
if (isRunAsCli(import.meta.url)) {
|
|
239
|
-
// Прямий запуск: `node skills/
|
|
240
|
-
|
|
193
|
+
// Прямий запуск: `node skills/doc-aggregate/js/docgen-scan.mjs modules --root <dir>`
|
|
194
|
+
const [sub, ...rest] = process.argv.slice(2)
|
|
195
|
+
process.exitCode = runDocAggregateModulesCli(sub === 'modules' ? rest : process.argv.slice(2))
|
|
241
196
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
docgen:
|
|
3
|
+
source: npm/skills/doc-aggregate/js/docgen-ignore.mjs
|
|
4
|
+
crc: 8821af65
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# docgen-ignore
|
|
8
|
+
|
|
9
|
+
## Огляд
|
|
10
|
+
|
|
11
|
+
Re-export спільного списку ignore-глобів зі скіла doc-files: обидва скіли документації (пофайлові доки й агрегати) мусять бачити однакове дерево кодових файлів, інакше агрегат посилатиметься на файли без док або навпаки.
|
|
12
|
+
|
|
13
|
+
## Поведінка
|
|
14
|
+
|
|
15
|
+
1. Модуль не має власної логіки: повністю делегує канонічному джерелу в doc-files і повторно експортує його API (список глобів і перевірку належності шляху до ігнорованих).
|
|
16
|
+
2. Напрям залежності — doc-aggregate → doc-files, відповідно до рішення про розбиття docgen на два скіли.
|
|
17
|
+
|
|
18
|
+
## Гарантії поведінки
|
|
19
|
+
|
|
20
|
+
- Списки ignore-глобів двох скілів не можуть розійтися — джерело одне.
|
|
21
|
+
- Read-only, без мережі й побічних ефектів.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doc-files
|
|
3
|
+
description: >-
|
|
4
|
+
Обовʼязковий крок задачі (як lint): для кожного зміненого/нового кодового файлу (js/mjs/ts/vue/py) JS-оркестрована генерація лаконічної поведінкової української md-документації у теку docs/ поряд із кодом, зі звіркою застарілості за CRC у frontmatter
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# doc-files — файлова документація (обовʼязковий крок)
|
|
8
|
+
|
|
9
|
+
## Мета
|
|
10
|
+
|
|
11
|
+
Для кожного кодового файлу проєкту тримати **актуальну** лаконічну поведінкову `.md`-документацію
|
|
12
|
+
у теці `docs/` **поряд із самим файлом** (`<dir>/docs/<stem>.md`). Це **обовʼязковий крок кожної
|
|
13
|
+
задачі** — як `lint`: після зміни коду його дока має бути перегенерована.
|
|
14
|
+
|
|
15
|
+
Застарілість визначається **детерміновано за CRC**: кожна дока несе у frontmatter контрольну
|
|
16
|
+
суму байтів джерела на момент генерації. Дока **застаріла**, якщо її немає або
|
|
17
|
+
`crc(поточне джерело) ≠ crc у frontmatter`.
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
---
|
|
21
|
+
docgen:
|
|
22
|
+
source: src/lib/foo.js
|
|
23
|
+
crc: a3f1c9e0
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Огляд
|
|
27
|
+
|
|
28
|
+
…
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Оркестрацію веде JS, не модель; конвеєр — local-only
|
|
32
|
+
|
|
33
|
+
Уся важка робота — черга, батчинг, виклики LLM і штамп CRC — живе в JS-команді
|
|
34
|
+
`doc-files gen`. **Ти не диспатчиш субагентів і не тримаєш сотні файлів у контексті**
|
|
35
|
+
— тому навіть масовий перший прогін усього репо не «заморює». Цей скіл **тонкий**: твоє завдання —
|
|
36
|
+
запустити генерацію і прочитати підсумок.
|
|
37
|
+
|
|
38
|
+
Конвеєр **суто локальний** (ADR `260610-2228`): будь-який файл генерується локальною
|
|
39
|
+
моделлю (`omlx/…` напряму), хмарних ескалацій немає. Якщо det-оцінка нижча за поріг —
|
|
40
|
+
дока все одно пишеться з **degraded-маркером** (`score`/`issues` у frontmatter, CRC свіжий),
|
|
41
|
+
а перегенерація таких док — окремою командою пізніше.
|
|
42
|
+
|
|
43
|
+
## Передумова
|
|
44
|
+
|
|
45
|
+
- Поточна директорія — корінь проєкту (`requireRoot`), не worktree.
|
|
46
|
+
- Доступний `npx @nitra/cursor`.
|
|
47
|
+
|
|
48
|
+
## Workflow
|
|
49
|
+
|
|
50
|
+
### Крок 1: Генерація застарілих/відсутніх док
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @nitra/cursor doc-files gen
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Команда сама: перевіряє omlx (preflight: «сервер лежить» / «модель не влазить у пам'ять
|
|
57
|
+
зайнятої машини» / «потрібен API-ключ» → одна зрозуміла зупинка замість лавини «✗») →
|
|
58
|
+
сканує проєкт → фільтрує застарілі (`stale`) → генерує локальною моделлю → пише доку зі
|
|
59
|
+
**свіжим CRC** (і degraded-маркером, якщо не дотягнула) → друкує прогрес і підсумок.
|
|
60
|
+
|
|
61
|
+
- Для дуже великого прогону можна порціями: `--from N --limit M`.
|
|
62
|
+
- Перегенерувати **всі** доки (не лише застарілі): `--overwrite`.
|
|
63
|
+
- Перегенерувати лише degraded (свіжі за CRC, score < порогу): `--retry-degraded`.
|
|
64
|
+
|
|
65
|
+
### Крок 2: Підтвердження
|
|
66
|
+
|
|
67
|
+
Дочекайся підсумку `✓ OK: <N> ⚠ degraded: <D> ✗ Err: <E>`. Якщо є помилки — перелічи
|
|
68
|
+
проблемні файли. Exit-код `1` означає, що хоча б один файл не згенерувався (або не пройшов
|
|
69
|
+
preflight). Degraded — не помилка: дока існує, борг видно у `check --degraded`.
|
|
70
|
+
|
|
71
|
+
### Крок 3 (за потреби): перевірка перед завершенням
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx @nitra/cursor doc-files check --git
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Перевіряє змінені у задачі джерела (`git diff --name-only HEAD`) проти CRC їхніх док.
|
|
78
|
+
Цю ж перевірку виконує **Stop-hook** як твердий гейт: завершити задачу зі застарілими
|
|
79
|
+
доками не можна (виняток — масовий прогін понад поріг `N_CURSOR_DOC_FILES_GATE_MAX`, дефолт 50).
|
|
80
|
+
Degraded-доки гейт **не** блокує (CRC свіжий); їх список — `doc-files check --degraded`.
|
|
81
|
+
|
|
82
|
+
## Правила стилю документа (за adr/ci4)
|
|
83
|
+
|
|
84
|
+
- Мова — **УКРАЇНСЬКА** для всього тексту. Code identifiers, шляхи, імена API, команди — як у коді.
|
|
85
|
+
- **Чистий Markdown.** Жодних HTML-обгорток. Єдиний виняток — машинний `docgen:`-frontmatter із CRC.
|
|
86
|
+
- **Фокус на ПОВЕДІНЦІ, не реалізації.** ЩО і НАВІЩО, а не як саме зроблено.
|
|
87
|
+
- Не перелічуй модулі стандартної бібліотеки і внутрішні назви допоміжних функцій.
|
|
88
|
+
- Кожна секція самодостатня (без «як вище», «ця функція»).
|
|
89
|
+
- Секції (лише доречні): `## Огляд`, `## Поведінка`, `## Публічний API`, `## Де використовується`,
|
|
90
|
+
`## Гарантії поведінки`; для `.vue` — `## Інтерфейс компонента`.
|
|
91
|
+
- Не вигадуй деталей, яких немає в коді.
|
|
92
|
+
|
|
93
|
+
## Нотатки
|
|
94
|
+
|
|
95
|
+
- Не комітити автоматично — користувач вирішує, коли комітити згенеровану доку.
|
|
96
|
+
- Scanner ігнорує `node_modules`, `dist`, `.git`, `__pycache__`, `coverage`, `.cursor`, `.claude`,
|
|
97
|
+
усі теки `docs/`, а також `*.test.*` / `*.spec.*` / `*.d.ts`. Кореневий repo `docs/` —
|
|
98
|
+
system-wide only: file-level docs туди не пишуться. Список glob-ів — `docgen-ignore.mjs`.
|
|
99
|
+
- Агрегуюча документація (module-summary, доменні доки) — окремий скіл `doc-aggregate`, за запитом.
|
|
100
|
+
- Для наявних док без CRC одноразово: `npx @nitra/cursor doc-files stamp` (штампує frontmatter без LLM).
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRC32 джерела + YAML-frontmatter файлової документації.
|
|
3
|
+
*
|
|
4
|
+
* Кожна файлова дока несе у frontmatter контрольну суму байтів джерела на момент
|
|
5
|
+
* генерації. Це детермінований маркер застарілості: `crc32(поточне джерело)` звіряється
|
|
6
|
+
* з `crc` у доці — розбіжність (або відсутня дока) означає, що дока відстала від коду.
|
|
7
|
+
* CRC не залежить від git-стану (rebase, незакомічене, гілки), тож придатний і для
|
|
8
|
+
* per-edit hook (бачить лише змінений файл), і для повного сканування.
|
|
9
|
+
*
|
|
10
|
+
* Degraded-маркер (ADR 260610-2228): якщо локальний конвеєр не дотягнув до порогу
|
|
11
|
+
* якості, дока все одно пишеться, а frontmatter додатково несе `score` (det-оцінка)
|
|
12
|
+
* та `issues` (коди проблем). CRC при цьому свіжий — Stop-гейт не блокує задачі через
|
|
13
|
+
* слабкість моделі; борг видимий через `check --degraded` і адресно перегенеровується
|
|
14
|
+
* через `gen --retry-degraded`.
|
|
15
|
+
*
|
|
16
|
+
* Frontmatter — єдиний дозволений виняток із правила «чистий Markdown без HTML»:
|
|
17
|
+
* це машинні метадані, не контент. Формат:
|
|
18
|
+
*
|
|
19
|
+
* ---
|
|
20
|
+
* docgen:
|
|
21
|
+
* source: src/lib/foo.js
|
|
22
|
+
* crc: a3f1c9e0
|
|
23
|
+
* score: 55
|
|
24
|
+
* issues: short-behavior,internal-name:bar
|
|
25
|
+
* ---
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
28
|
+
import { crc32 as zlibCrc32 } from 'node:zlib'
|
|
29
|
+
import { env } from 'node:process'
|
|
30
|
+
|
|
31
|
+
/** Поріг degraded: дока зі `score` нижче вважається неякісною. */
|
|
32
|
+
export const QUALITY_THRESHOLD = Number(env.N_CURSOR_DOC_FILES_THRESHOLD ?? 70) || 70
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* CRC32 вмісту у hex (8 символів, з провідними нулями). Делегує у нативний
|
|
36
|
+
* `node:zlib.crc32` — без ручної бітової арифметики.
|
|
37
|
+
* @param {string|Buffer} input текст або байти джерела
|
|
38
|
+
* @returns {string} CRC32 у hex
|
|
39
|
+
*/
|
|
40
|
+
export function crc32(input) {
|
|
41
|
+
const buf = typeof input === 'string' ? Buffer.from(input, 'utf8') : input
|
|
42
|
+
return zlibCrc32(buf).toString(16).padStart(8, '0')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Провідний YAML-frontmatter-блок `---\n…\n---`. */
|
|
46
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/u
|
|
47
|
+
const SOURCE_RE = /^[ \t]{0,8}source:[ \t]{0,8}(.+)$/mu
|
|
48
|
+
const CRC_RE = /^[ \t]{0,8}crc:[ \t]{0,8}(.+)$/mu
|
|
49
|
+
const SCORE_RE = /^[ \t]{0,8}score:[ \t]{0,8}(\d+)$/mu
|
|
50
|
+
const ISSUES_RE = /^[ \t]{0,8}issues:[ \t]{0,8}(.+)$/mu
|
|
51
|
+
const LEADING_NEWLINES_RE = /^\n+/u
|
|
52
|
+
const ISSUE_CODE_TAIL_RE = /[,:]$/u
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Парсить frontmatter файлової доки. Без блоку — `data:null` і `body` дорівнює входу.
|
|
56
|
+
* Поля `score`/`issues` опційні (back-compat зі старими доками): без них —
|
|
57
|
+
* `score:null`, `issues:[]`.
|
|
58
|
+
* @param {string} md вміст md-файлу
|
|
59
|
+
* @returns {{ data: { source: string|null, crc: string|null, score: number|null, issues: string[] }|null, body: string }} метадані + тіло без frontmatter
|
|
60
|
+
*/
|
|
61
|
+
export function parseDocFrontmatter(md) {
|
|
62
|
+
const match = md.match(FRONTMATTER_RE)
|
|
63
|
+
if (!match) return { data: null, body: md }
|
|
64
|
+
const block = match[1]
|
|
65
|
+
const scoreRaw = block.match(SCORE_RE)?.[1]
|
|
66
|
+
const issuesRaw = block.match(ISSUES_RE)?.[1]
|
|
67
|
+
return {
|
|
68
|
+
data: {
|
|
69
|
+
source: block.match(SOURCE_RE)?.[1].trim() ?? null,
|
|
70
|
+
crc: block.match(CRC_RE)?.[1].trim() ?? null,
|
|
71
|
+
score: scoreRaw === undefined ? null : Number(scoreRaw),
|
|
72
|
+
issues: issuesRaw
|
|
73
|
+
? issuesRaw
|
|
74
|
+
.split(',')
|
|
75
|
+
.map(s => s.trim())
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
: []
|
|
78
|
+
},
|
|
79
|
+
body: md.slice(match[0].length)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Максимум кодів issues у frontmatter — це маркер, а не повний лог. */
|
|
84
|
+
const MAX_ISSUE_CODES = 8
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Нормалізує issues до YAML-безпечних кодів: бере фрагмент до першого пробілу
|
|
88
|
+
* (зрізає людиночитні хвости помилок), відкидає порожні, обмежує кількість.
|
|
89
|
+
* @param {string[]} issues сирі issue-рядки від скорера
|
|
90
|
+
* @returns {string[]} коди без пробілів
|
|
91
|
+
*/
|
|
92
|
+
function issueCodes(issues) {
|
|
93
|
+
return issues
|
|
94
|
+
.map(i => String(i).split(' ')[0].replace(ISSUE_CODE_TAIL_RE, ''))
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.slice(0, MAX_ISSUE_CODES)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Будує frontmatter-блок із шляхом джерела, CRC і (опційно) якістю.
|
|
101
|
+
* @param {string} source відносний шлях джерела
|
|
102
|
+
* @param {string} crc CRC32 джерела у hex
|
|
103
|
+
* @param {{ score: number, issues?: string[] }|null} [quality] det-оцінка доки; null — без полів якості
|
|
104
|
+
* @returns {string} рядок `---\ndocgen:\n source: …\n crc: …[\n score: …][\n issues: …]\n---\n`
|
|
105
|
+
*/
|
|
106
|
+
export function buildDocFrontmatter(source, crc, quality = null) {
|
|
107
|
+
const lines = [`source: ${source}`, `crc: ${crc}`]
|
|
108
|
+
if (quality && typeof quality.score === 'number') {
|
|
109
|
+
lines.push(`score: ${quality.score}`)
|
|
110
|
+
const codes = issueCodes(quality.issues ?? [])
|
|
111
|
+
if (codes.length > 0) lines.push(`issues: ${codes.join(',')}`)
|
|
112
|
+
}
|
|
113
|
+
const indented = lines.map(l => ' ' + l).join('\n')
|
|
114
|
+
return `---\ndocgen:\n${indented}\n---\n`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* (Пере)штампує frontmatter у md-доку: знімає наявний блок і додає свіжий.
|
|
119
|
+
* @param {string} md тіло доки (з frontmatter або без)
|
|
120
|
+
* @param {string} source відносний шлях джерела
|
|
121
|
+
* @param {string} crc CRC32 джерела у hex
|
|
122
|
+
* @param {{ score: number, issues?: string[] }|null} [quality] det-оцінка доки
|
|
123
|
+
* @returns {string} md зі свіжим frontmatter
|
|
124
|
+
*/
|
|
125
|
+
export function stampDoc(md, source, crc, quality = null) {
|
|
126
|
+
const { body } = parseDocFrontmatter(md)
|
|
127
|
+
return `${buildDocFrontmatter(source, crc, quality)}\n${body.replace(LEADING_NEWLINES_RE, '')}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* CRC, збережений у frontmatter доки; `null` — доки немає або CRC відсутній.
|
|
132
|
+
* @param {string} docAbsPath абсолютний шлях md-доки
|
|
133
|
+
* @returns {string|null} CRC32 з frontmatter або null
|
|
134
|
+
*/
|
|
135
|
+
export function readDocCrc(docAbsPath) {
|
|
136
|
+
if (!existsSync(docAbsPath)) return null
|
|
137
|
+
return parseDocFrontmatter(readFileSync(docAbsPath, 'utf8')).data?.crc ?? null
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Якість, збережена у frontmatter доки.
|
|
142
|
+
* @param {string} docAbsPath абсолютний шлях md-доки
|
|
143
|
+
* @returns {{ score: number|null, issues: string[] }} `score:null` — доки немає або поле відсутнє
|
|
144
|
+
*/
|
|
145
|
+
export function readDocQuality(docAbsPath) {
|
|
146
|
+
if (!existsSync(docAbsPath)) return { score: null, issues: [] }
|
|
147
|
+
const data = parseDocFrontmatter(readFileSync(docAbsPath, 'utf8')).data
|
|
148
|
+
return { score: data?.score ?? null, issues: data?.issues ?? [] }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Стан застарілості доки відносно її джерела.
|
|
153
|
+
* `missing` — доки немає; `crc-mismatch` — CRC джерела ≠ CRC у доці; інакше свіжа.
|
|
154
|
+
* @param {string} sourceAbsPath абсолютний шлях джерела
|
|
155
|
+
* @param {string} docAbsPath абсолютний шлях md-доки
|
|
156
|
+
* @returns {{ stale: boolean, reason: 'missing'|'crc-mismatch'|null }} стан застарілості
|
|
157
|
+
*/
|
|
158
|
+
export function staleness(sourceAbsPath, docAbsPath) {
|
|
159
|
+
const docCrc = readDocCrc(docAbsPath)
|
|
160
|
+
if (docCrc === null) return { stale: true, reason: 'missing' }
|
|
161
|
+
const srcCrc = crc32(readFileSync(sourceAbsPath))
|
|
162
|
+
if (srcCrc !== docCrc) return { stale: true, reason: 'crc-mismatch' }
|
|
163
|
+
return { stale: false, reason: null }
|
|
164
|
+
}
|
|
@@ -19,9 +19,13 @@ const EXPORT_CONST_RE = /export\s+const\s+([A-Z][A-Z0-9_]+)\s*=\s*(['"`])([^'"`]
|
|
|
19
19
|
const ERROR_MARKER_RE = /\(([a-z][\w-]*\.mdc)\)/g
|
|
20
20
|
const CONFIG_REF_RE = /\b(\.[a-z][\w.-]*\.json)\b/gi
|
|
21
21
|
const FILE_HEADER_RE = /^\s*\/\*\*([\s\S]*?)\*\//
|
|
22
|
-
const CODE_BLOCK_RE = /```[a-z]
|
|
22
|
+
const CODE_BLOCK_RE = /```[a-z]{0,12}\n([\s\S]*?)\n[ \t]{0,8}\*?[ \t]{0,8}```/g
|
|
23
23
|
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Dedup масив, зберігаючи порядок появи.
|
|
26
|
+
* @param {Array<string>} arr вхідний масив
|
|
27
|
+
* @returns {Array<string>} масив без дублікатів у вихідному порядку
|
|
28
|
+
*/
|
|
25
29
|
function uniq(arr) {
|
|
26
30
|
const seen = new Set()
|
|
27
31
|
const out = []
|
|
@@ -36,14 +40,14 @@ function uniq(arr) {
|
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
42
|
* Витягує анкори з вихідного коду файла.
|
|
39
|
-
* @param {string} src
|
|
43
|
+
* @param {string} src вміст файлу
|
|
40
44
|
* @returns {{
|
|
41
45
|
* urls: string[],
|
|
42
46
|
* magicStrings: Array<{name:string, value:string}>,
|
|
43
47
|
* errorMarkers: string[],
|
|
44
48
|
* configRefs: string[],
|
|
45
49
|
* examples: string[]
|
|
46
|
-
* }}
|
|
50
|
+
* }} категоризовані анкори файлу
|
|
47
51
|
*/
|
|
48
52
|
export function extractAnchors(src) {
|
|
49
53
|
const urls = uniq(Array.from(src.matchAll(URL_RE), m => m[0]))
|
|
@@ -73,20 +77,25 @@ export function extractAnchors(src) {
|
|
|
73
77
|
* Форматує анкори у компактний текст для system-промпта.
|
|
74
78
|
* Якщо анкорів немає взагалі — повертає порожній рядок (системний блок про
|
|
75
79
|
* анкори не додається, щоб не вводити LLM в оману «обовʼязковими» полями).
|
|
76
|
-
* @param {ReturnType<typeof extractAnchors>} a
|
|
77
|
-
* @returns {string}
|
|
80
|
+
* @param {ReturnType<typeof extractAnchors>} a анкори файлу
|
|
81
|
+
* @returns {string} компактний текстовий блок або порожній рядок
|
|
78
82
|
*/
|
|
79
83
|
export function anchorsToPrompt(a) {
|
|
80
84
|
const blocks = []
|
|
81
85
|
if (a.urls.length) blocks.push(`URLs (згадай у тексті): ${a.urls.join(', ')}`)
|
|
82
86
|
if (a.magicStrings.length) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
const consts = a.magicStrings.map(s => s.name + '=' + JSON.stringify(s.value)).join('; ')
|
|
88
|
+
blocks.push(`Експортовані константи-рядки (наведи назву і призначення): ${consts}`)
|
|
89
|
+
}
|
|
90
|
+
if (a.errorMarkers.length) {
|
|
91
|
+
const markers = a.errorMarkers.map(m => '(' + m + ')').join(', ')
|
|
92
|
+
blocks.push(`Маркери повідомлень (згадай у Поведінці): ${markers}`)
|
|
86
93
|
}
|
|
87
|
-
if (a.errorMarkers.length) blocks.push(`Маркери повідомлень (згадай у Поведінці): ${a.errorMarkers.map(m => `(${m})`).join(', ')}`)
|
|
88
94
|
if (a.configRefs.length) blocks.push(`Конфіги, на які спирається код: ${a.configRefs.join(', ')}`)
|
|
89
|
-
if (a.examples.length)
|
|
95
|
+
if (a.examples.length) {
|
|
96
|
+
const fenced = a.examples.map(e => '```\n' + e + '\n```').join('\n')
|
|
97
|
+
blocks.push(`Приклади з документації автора (наведи дослівно у Поведінці):\n${fenced}`)
|
|
98
|
+
}
|
|
90
99
|
if (!blocks.length) return ''
|
|
91
100
|
return `АНКОРИ ДО ОБОВ'ЯЗКОВОГО ВКЛЮЧЕННЯ:\n${blocks.join('\n')}`
|
|
92
101
|
}
|
|
@@ -25,15 +25,17 @@ const BUILTIN_MODULES = new Set([
|
|
|
25
25
|
const JSDOC_OPEN_RE = /^\s*\/\*\*?/
|
|
26
26
|
const JSDOC_CLOSE_RE = /\*\/\s*$/
|
|
27
27
|
const STAR_PREFIX_RE = /^\s*\*?\s?/
|
|
28
|
-
const PARAM_LINE_RE = /^@param\
|
|
29
|
-
const RETURNS_LINE_RE = /^@returns
|
|
28
|
+
const PARAM_LINE_RE = /^@param[ \t]{1,8}(?:\{[^}]{0,200}\}[ \t]{1,8})?\[?([\w.]{1,80})\]?[ \t]{0,8}(.{0,400})$/
|
|
29
|
+
const RETURNS_LINE_RE = /^@returns?[ \t]{1,8}(?:\{[^}]{0,200}\}[ \t]{1,8})?(.{0,400})$/
|
|
30
30
|
const FILE_HEADER_RE = /^\s*\/\*\*([\s\S]*?)\*\//
|
|
31
31
|
const PRECEDING_JSDOC_RE = /\/\*\*(?:(?!\*\/)[\s\S])*\*\/\s*$/
|
|
32
|
-
const EXPORT_DECL_RE = /export\s+(?:async\s+)?(function|const|class)\s+(
|
|
33
|
-
const IMPORT_FROM_RE = /^import\
|
|
32
|
+
const EXPORT_DECL_RE = /export\s+(?:async\s+)?(function|const|class)\s+(\w+)/g
|
|
33
|
+
const IMPORT_FROM_RE = /^import[ \t]{1,8}[\s\S]{0,300}?from\s{1,8}['"]([^'"]+)['"]/gm
|
|
34
34
|
const NODE_PREFIX_RE = /^node:/
|
|
35
|
-
const INTERNAL_IMPORT_RE = /import\
|
|
36
|
-
const
|
|
35
|
+
const INTERNAL_IMPORT_RE = /import[ \t]{1,8}([^'"]{0,300}?)from[ \t]{1,8}['"]\.[^'"]{1,300}['"]/g
|
|
36
|
+
const NAMED_BRACES_RE = /\{([^}]{1,400})\}/
|
|
37
|
+
const IDENT_RE = /^[\w$]{1,80}$/
|
|
38
|
+
const IMPORT_AS_RE = /[ \t]{1,8}as[ \t]{1,8}.{0,200}/
|
|
37
39
|
const WRITE_FS_RE = /\b(writeFile|mkdir|rmdir|unlink|appendFile|createWriteStream|rm\()/
|
|
38
40
|
const CATCH_RE = /catch\s*\(/
|
|
39
41
|
const TRY_RE = /\btry\s*\{/
|
|
@@ -152,12 +154,16 @@ function extractImports(src) {
|
|
|
152
154
|
function extractInternalSymbols(src) {
|
|
153
155
|
const out = new Set()
|
|
154
156
|
for (const m of src.matchAll(INTERNAL_IMPORT_RE)) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
const clause = m[1]
|
|
158
|
+
const named = clause.match(NAMED_BRACES_RE)
|
|
159
|
+
if (named) {
|
|
160
|
+
for (const n of named[1].split(',')) {
|
|
158
161
|
const name = n.replace(IMPORT_AS_RE, '').trim()
|
|
159
162
|
if (name) out.add(name)
|
|
160
163
|
}
|
|
164
|
+
}
|
|
165
|
+
const defName = clause.replace(NAMED_BRACES_RE, '').replaceAll(',', ' ').trim().split(' ')[0]
|
|
166
|
+
if (defName && IDENT_RE.test(defName)) out.add(defName)
|
|
161
167
|
}
|
|
162
168
|
return [...out]
|
|
163
169
|
}
|