@nitra/cursor 5.3.2 → 5.3.4
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/lib/docs/llm.md +23 -12
- package/package.json +1 -1
- package/rules/abie/fix.mjs +1 -2
- package/rules/adr/fix.mjs +1 -2
- package/rules/bun/fix.mjs +1 -2
- package/rules/capacitor/fix.mjs +1 -2
- package/rules/capacitor/js/platforms.mjs +0 -2
- package/rules/changelog/fix.mjs +1 -2
- package/rules/ci4/fix.mjs +1 -2
- package/rules/docker/fix.mjs +1 -2
- package/rules/efes/fix.mjs +1 -2
- package/rules/feedback/fix.mjs +1 -2
- package/rules/ga/fix.mjs +1 -2
- package/rules/graphql/fix.mjs +1 -2
- package/rules/hasura/fix.mjs +1 -2
- package/rules/image-avif/fix.mjs +1 -2
- package/rules/image-compress/fix.mjs +1 -2
- package/rules/js-bun-db/fix.mjs +1 -2
- package/rules/js-bun-redis/fix.mjs +1 -2
- package/rules/js-lint/fix.mjs +1 -2
- package/rules/js-lint-ci/fix.mjs +1 -2
- package/rules/js-mssql/fix.mjs +1 -2
- package/rules/js-run/fix.mjs +1 -2
- package/rules/k8s/fix.mjs +1 -2
- package/rules/k8s/js/manifests.mjs +1 -5
- package/rules/nginx-default-tpl/fix.mjs +1 -2
- package/rules/npm-module/fix.mjs +1 -2
- package/rules/npm-module/js/package_structure.mjs +0 -1
- package/rules/php/fix.mjs +1 -2
- package/rules/python/fix.mjs +1 -2
- package/rules/rego/fix.mjs +1 -2
- package/rules/release/fix.mjs +1 -2
- package/rules/rust/fix.mjs +1 -2
- package/rules/security/fix.mjs +1 -2
- package/rules/style-lint/fix.mjs +1 -2
- package/rules/tauri/fix.mjs +1 -2
- package/rules/test/coverage/coverage.mjs +0 -2
- package/rules/test/fix.mjs +1 -2
- package/rules/text/fix.mjs +1 -2
- package/rules/vue/fix.mjs +1 -2
- package/rules/worktree/fix.mjs +1 -2
- package/scripts/lib/run-rule.mjs +0 -2
- package/scripts/lint-cli.mjs +0 -1
- package/scripts/utils/with-lock.mjs +0 -1
- package/skills/doc-aggregate/js/docgen-scan.mjs +17 -18
- package/skills/doc-files/.changes/260612-0002.md +5 -0
- package/skills/doc-files/.changes/260612-0006.md +5 -0
- package/skills/doc-files/.changes/260612-0008.md +5 -0
- package/skills/doc-files/.changes/260612-0012.md +5 -0
- package/skills/doc-files/js/docgen-extract.mjs +136 -0
- package/skills/doc-files/js/docgen-prompts.mjs +2 -2
- package/skills/doc-files/js/docgen-scan.mjs +21 -22
- package/skills/doc-files/js/docs/docgen-extract-anchors.md +28 -10
- package/skills/doc-files/js/docs/docgen-extract.md +22 -12
- package/skills/doc-files/js/docs/docgen-files-batch.md +21 -11
- package/skills/doc-files/js/docs/docgen-gen.md +29 -13
- package/skills/doc-files/js/docs/docgen-ignore.md +37 -0
- package/skills/doc-files/js/docs/units-rs.md +35 -0
- package/skills/doc-files/js/units-rs.mjs +213 -0
- package/skills/doc-files/js/units.mjs +4 -3
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** @see ./docs/docgen-scan.md */
|
|
2
|
-
|
|
3
|
-
import path from 'node:path'
|
|
2
|
+
import { join, relative, dirname, extname, sep, isAbsolute, resolve } from 'node:path'
|
|
4
3
|
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
5
4
|
|
|
6
5
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
@@ -18,7 +17,7 @@ const TEST_FILE_RE = /\.(?:test|spec)\.[^.]+$/u
|
|
|
18
17
|
* @returns {boolean} true — корінь system-wide docs
|
|
19
18
|
*/
|
|
20
19
|
function isSystemWideDocsRoot(root) {
|
|
21
|
-
return existsSync(
|
|
20
|
+
return existsSync(join(root, 'docs', 'adr')) || existsSync(join(root, 'docs', 'explanation'))
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -29,7 +28,7 @@ function isSystemWideDocsRoot(root) {
|
|
|
29
28
|
export function isSourceFile(fileName) {
|
|
30
29
|
if (fileName.endsWith('.d.ts')) return false
|
|
31
30
|
if (TEST_FILE_RE.test(fileName)) return false
|
|
32
|
-
return SOURCE_EXTENSIONS.has(
|
|
31
|
+
return SOURCE_EXTENSIONS.has(extname(fileName))
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
/**
|
|
@@ -49,14 +48,14 @@ export function scanSourceFiles(root) {
|
|
|
49
48
|
return
|
|
50
49
|
}
|
|
51
50
|
for (const entry of entries) {
|
|
52
|
-
const fullPath =
|
|
53
|
-
const relPath =
|
|
51
|
+
const fullPath = join(dir, entry.name)
|
|
52
|
+
const relPath = relative(root, fullPath)
|
|
54
53
|
if (entry.isDirectory()) {
|
|
55
54
|
if (isDocgenIgnored(relPath, 'dir')) continue
|
|
56
55
|
walk(fullPath)
|
|
57
56
|
} else if (entry.isFile() && isSourceFile(entry.name)) {
|
|
58
|
-
if (isSystemWideDocsRoot(root) &&
|
|
59
|
-
const sourcePath = relPath.split(
|
|
57
|
+
if (isSystemWideDocsRoot(root) && dirname(relPath) === '.') continue
|
|
58
|
+
const sourcePath = relPath.split(sep).join('/')
|
|
60
59
|
if (isDocgenIgnored(sourcePath)) continue
|
|
61
60
|
results.push(sourcePath)
|
|
62
61
|
}
|
|
@@ -74,10 +73,10 @@ export function scanSourceFiles(root) {
|
|
|
74
73
|
* @returns {string} slug: `npm/rules/adr` → `npm-rules-adr`, корінь → `root`
|
|
75
74
|
*/
|
|
76
75
|
export function slugForModule(root, moduleRoot) {
|
|
77
|
-
const rel =
|
|
76
|
+
const rel = relative(root, moduleRoot)
|
|
78
77
|
if (rel === '') return 'root'
|
|
79
78
|
return rel
|
|
80
|
-
.split(
|
|
79
|
+
.split(sep)
|
|
81
80
|
.join('-')
|
|
82
81
|
.replaceAll(/[^\w-]+/gu, '-')
|
|
83
82
|
}
|
|
@@ -99,8 +98,8 @@ export function findModuleRoots(root) {
|
|
|
99
98
|
return
|
|
100
99
|
}
|
|
101
100
|
for (const entry of entries) {
|
|
102
|
-
const fullPath =
|
|
103
|
-
const relPath =
|
|
101
|
+
const fullPath = join(dir, entry.name)
|
|
102
|
+
const relPath = relative(root, fullPath)
|
|
104
103
|
if (entry.isDirectory()) {
|
|
105
104
|
if (isDocgenIgnored(relPath, 'dir')) continue
|
|
106
105
|
walk(fullPath)
|
|
@@ -123,8 +122,8 @@ export function findModuleRoots(root) {
|
|
|
123
122
|
export function nearestModuleRoot(filePath, moduleRoots) {
|
|
124
123
|
let best = null
|
|
125
124
|
for (const moduleRoot of moduleRoots) {
|
|
126
|
-
const rel =
|
|
127
|
-
if (rel.startsWith('..') ||
|
|
125
|
+
const rel = relative(moduleRoot, filePath)
|
|
126
|
+
if (rel.startsWith('..') || isAbsolute(rel)) continue
|
|
128
127
|
if (best === null || moduleRoot.length > best.length) best = moduleRoot
|
|
129
128
|
}
|
|
130
129
|
return best
|
|
@@ -141,7 +140,7 @@ export function scanForModules(root) {
|
|
|
141
140
|
const moduleRoots = findModuleRoots(root)
|
|
142
141
|
const byRoot = new Map()
|
|
143
142
|
for (const sourcePath of files) {
|
|
144
|
-
const moduleRoot = nearestModuleRoot(
|
|
143
|
+
const moduleRoot = nearestModuleRoot(join(root, sourcePath), moduleRoots)
|
|
145
144
|
if (moduleRoot === null) continue
|
|
146
145
|
if (!byRoot.has(moduleRoot)) byRoot.set(moduleRoot, [])
|
|
147
146
|
byRoot.get(moduleRoot).push(sourcePath)
|
|
@@ -151,10 +150,10 @@ export function scanForModules(root) {
|
|
|
151
150
|
for (const moduleRoot of moduleRoots) {
|
|
152
151
|
const members = byRoot.get(moduleRoot)
|
|
153
152
|
if (!members || members.length === 0) continue
|
|
154
|
-
const docPath =
|
|
153
|
+
const docPath = join(moduleRoot, 'docs', 'ARCHITECTURE.md')
|
|
155
154
|
results.push({
|
|
156
155
|
moduleRoot,
|
|
157
|
-
relRoot:
|
|
156
|
+
relRoot: relative(root, moduleRoot) || '.',
|
|
158
157
|
slug: slugForModule(root, moduleRoot),
|
|
159
158
|
docPath,
|
|
160
159
|
members: members.toSorted(),
|
|
@@ -171,7 +170,7 @@ export function scanForModules(root) {
|
|
|
171
170
|
*/
|
|
172
171
|
export function resolveRoot(argv) {
|
|
173
172
|
const i = argv.indexOf('--root')
|
|
174
|
-
return i !== -1 && argv[i + 1] ?
|
|
173
|
+
return i !== -1 && argv[i + 1] ? resolve(argv[i + 1]) : process.cwd()
|
|
175
174
|
}
|
|
176
175
|
|
|
177
176
|
/**
|
|
@@ -211,6 +211,141 @@ function extractMarkers(src) {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
// ── Rust-екстрактор ──────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
// Модульний //! doc-коментар (inner doc)
|
|
217
|
+
const RS_MODULE_DOC_RE = /^(?:[ \t]*\/\/![ \t]?(.*)\n)*/m
|
|
218
|
+
|
|
219
|
+
// pub fn / pub struct / pub enum / pub trait (та fn із exposure-атрибутом)
|
|
220
|
+
const RS_PUB_ITEM_RE = /^[ \t]*(pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|type)\s+(\w+)/gm
|
|
221
|
+
|
|
222
|
+
// Exposure-атрибути (#[tauri::command] тощо)
|
|
223
|
+
const RS_EXPOSURE_ATTR_RE = /#\[(?:tauri::command|wasm_bindgen|uniffi::export|pyo3::pyfunction|napi)/gm
|
|
224
|
+
|
|
225
|
+
// /// line-doc перед елементом
|
|
226
|
+
const RS_LINE_DOC_RE = /(?:[ \t]*\/\/\/[ \t]?.*\n)*/
|
|
227
|
+
|
|
228
|
+
// use crate::module::{A, B} або use std::..;
|
|
229
|
+
const RS_USE_RE = /^[ \t]*use\s+([\w:]+(?:::\{[^}]+\})?(?:::\*)?(?:::\w+)?)\s*;/gm
|
|
230
|
+
|
|
231
|
+
// Файловий запис: fs::write / File::create / remove_file / create_dir / write_all
|
|
232
|
+
const RS_WRITE_RE = /fs::write|File::create|remove_file|create_dir|BufWriter::new|OpenOptions[^;]*\.write\s*\(\s*true/
|
|
233
|
+
|
|
234
|
+
// Обробка помилок (але не просто `?`)
|
|
235
|
+
const RS_CATCH_RE = /\.unwrap_or(?:_else|_default)?|if\s+let\s+Err\s*\(|match\s+\S+.*\{\s*[\s\S]*?Err\s*\(|\.map_err\s*\(|\.ok\s*\(\)/
|
|
236
|
+
|
|
237
|
+
// Функції, що повертають Result або Option
|
|
238
|
+
const RS_RESULT_RE = /->\s*(?:Result|Option)\s*</
|
|
239
|
+
|
|
240
|
+
// Мережа
|
|
241
|
+
const RS_NETWORK_RE = /reqwest|hyper::|TcpStream|UdpSocket|tokio::net/
|
|
242
|
+
|
|
243
|
+
// Кешування
|
|
244
|
+
const RS_CACHE_RE = /\bcache\b|\bCache\b|lazy_static!|OnceCell|OnceLock|DashMap/i
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Видобуває `///` doc-рядки перед рядком `lineIdx` (назад через `#[...]` та пусті рядки).
|
|
248
|
+
* @param {string[]} lines рядки файлу
|
|
249
|
+
* @param {number} lineIdx індекс рядка декларації
|
|
250
|
+
* @returns {string} опис або ''
|
|
251
|
+
*/
|
|
252
|
+
function rsDocBefore(lines, lineIdx) {
|
|
253
|
+
const doc = []
|
|
254
|
+
for (let i = lineIdx - 1; i >= 0; i--) {
|
|
255
|
+
const t = lines[i].trim()
|
|
256
|
+
if (t.startsWith('///')) doc.unshift(t.slice(3).trim())
|
|
257
|
+
else if (t.startsWith('#[') || t.startsWith('#![') || t === '') { /* skip */ }
|
|
258
|
+
else break
|
|
259
|
+
}
|
|
260
|
+
return doc.join(' ').trim()
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Витягує факт-лист для `.rs` файлу.
|
|
265
|
+
* @param {string} src вміст файлу
|
|
266
|
+
* @param {string} relPath відносний шлях
|
|
267
|
+
* @returns {object} факт-лист без `unsupported`
|
|
268
|
+
*/
|
|
269
|
+
function extractFactsRust(src, relPath) {
|
|
270
|
+
// header — //! module-level doc
|
|
271
|
+
const headerLines = []
|
|
272
|
+
for (const line of src.split('\n')) {
|
|
273
|
+
const t = line.trim()
|
|
274
|
+
if (t.startsWith('//!')) headerLines.push(t.slice(3).trim())
|
|
275
|
+
else if (t === '' || t.startsWith('//')) continue
|
|
276
|
+
else break
|
|
277
|
+
}
|
|
278
|
+
const header = headerLines.join(' ').trim()
|
|
279
|
+
|
|
280
|
+
// Exposure-атрибути: рядки, після яких fn стає фактично pub
|
|
281
|
+
const srcLines = src.split('\n')
|
|
282
|
+
const exposedLineSet = new Set()
|
|
283
|
+
for (const m of src.matchAll(RS_EXPOSURE_ATTR_RE)) {
|
|
284
|
+
// Знаходимо, який рядок містить цей атрибут
|
|
285
|
+
let pos = 0
|
|
286
|
+
for (let li = 0; li < srcLines.length; li++) {
|
|
287
|
+
if (pos + srcLines[li].length >= m.index) {
|
|
288
|
+
// Шукаємо наступний не-атрибутний рядок з fn
|
|
289
|
+
for (let nli = li + 1; nli < Math.min(li + 5, srcLines.length); nli++) {
|
|
290
|
+
const t = srcLines[nli].trim()
|
|
291
|
+
if (t.startsWith('#[') || t === '') continue
|
|
292
|
+
if (/^(?:pub\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+/.test(t)) exposedLineSet.add(nli)
|
|
293
|
+
break
|
|
294
|
+
}
|
|
295
|
+
break
|
|
296
|
+
}
|
|
297
|
+
pos += srcLines[li].length + 1
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// exports — pub items + exposure-exposed fns
|
|
302
|
+
const exports = []
|
|
303
|
+
let lineOffset = 0
|
|
304
|
+
for (let li = 0; li < srcLines.length; li++) {
|
|
305
|
+
const line = srcLines[li]
|
|
306
|
+
const m = line.match(/^[ \t]*(pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(fn|struct|enum|trait|type)\s+(\w+)/)
|
|
307
|
+
if (m) {
|
|
308
|
+
const isPub = Boolean(m[1]) || exposedLineSet.has(li)
|
|
309
|
+
if (isPub) {
|
|
310
|
+
const desc = rsDocBefore(srcLines, li)
|
|
311
|
+
exports.push({ name: m[3], kind: m[2], desc })
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
lineOffset += line.length + 1
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// localSymbols — приватні fn (не pub і не exposed) — не документуємо як публічний API
|
|
318
|
+
const localSymbols = []
|
|
319
|
+
for (let li = 0; li < srcLines.length; li++) {
|
|
320
|
+
const line = srcLines[li]
|
|
321
|
+
const m = line.match(/^[ \t]*(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)/)
|
|
322
|
+
if (m && !exports.some(e => e.name === m[1])) localSymbols.push(m[1])
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// imports — use-рядки, класифіковані на std / external / internal
|
|
326
|
+
const stdlib = new Set()
|
|
327
|
+
const external = new Set()
|
|
328
|
+
for (const m of src.matchAll(RS_USE_RE)) {
|
|
329
|
+
const path = m[1]
|
|
330
|
+
const root = path.split('::')[0]
|
|
331
|
+
if (root === 'std' || root === 'core' || root === 'alloc') stdlib.add(path)
|
|
332
|
+
else external.add(path)
|
|
333
|
+
}
|
|
334
|
+
const imports = { stdlib: [...stdlib], external: [...external], internal: [] }
|
|
335
|
+
|
|
336
|
+
// markers
|
|
337
|
+
const markers = {
|
|
338
|
+
readOnly: !RS_WRITE_RE.test(src),
|
|
339
|
+
catchesErrors: RS_CATCH_RE.test(src),
|
|
340
|
+
returnsFalsyOnFail: RS_RESULT_RE.test(src),
|
|
341
|
+
network: RS_NETWORK_RE.test(src),
|
|
342
|
+
caches: RS_CACHE_RE.test(src),
|
|
343
|
+
skips: []
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { relPath, lang: 'rs', header, exports, imports, internalSymbols: [], localSymbols, markers }
|
|
347
|
+
}
|
|
348
|
+
|
|
214
349
|
/**
|
|
215
350
|
* Головний екстрактор: код файлу → факт-лист.
|
|
216
351
|
* @param {string} src вміст файлу
|
|
@@ -219,6 +354,7 @@ function extractMarkers(src) {
|
|
|
219
354
|
*/
|
|
220
355
|
export function extractFacts(src, relPath) {
|
|
221
356
|
const lang = relPath.split('.').pop()
|
|
357
|
+
if (lang === 'rs') return extractFactsRust(src, relPath)
|
|
222
358
|
if (!['js', 'mjs', 'ts'].includes(lang)) {
|
|
223
359
|
return { relPath, lang, unsupported: true, header: '', exports: [], imports: {}, markers: {} }
|
|
224
360
|
}
|
|
@@ -32,7 +32,7 @@ function factsSummary(facts) {
|
|
|
32
32
|
if (m.skips?.length) lines.push(`Свідомо пропускає шляхи: ${m.skips.join(', ')}`)
|
|
33
33
|
lines.push(`Read-only: ${m.readOnly ? 'так' : 'ні'}`)
|
|
34
34
|
if (m.catchesErrors) lines.push('Перехоплює помилки (fail-safe), не кидає винятків назовні')
|
|
35
|
-
if (m.returnsFalsyOnFail) lines.push('За невдачі повертає false/null замість винятку')
|
|
35
|
+
if (m.returnsFalsyOnFail) lines.push('За невдачі повертає значення помилки (false/null/Err) замість винятку чи паніки')
|
|
36
36
|
lines.push(m.caches ? 'Кешування: так, у межах прогону' : 'Кешування: НЕМАЄ — не згадуй кеш у гарантіях')
|
|
37
37
|
if (m.network) lines.push('Звертається до мережі')
|
|
38
38
|
else lines.push('Робота з мережею: немає')
|
|
@@ -195,7 +195,7 @@ export function guaranteesFromMarkers(facts) {
|
|
|
195
195
|
const lines = []
|
|
196
196
|
if (m.readOnly) lines.push('- Read-only: файл не виконує операцій запису у файлову систему.')
|
|
197
197
|
if (m.catchesErrors) lines.push('- Перехоплює помилки і не пропускає винятків назовні (fail-safe).')
|
|
198
|
-
if (m.returnsFalsyOnFail) lines.push('- За
|
|
198
|
+
if (m.returnsFalsyOnFail) lines.push('- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.')
|
|
199
199
|
if (m.caches) lines.push('- Кешує результати в межах одного прогону.')
|
|
200
200
|
if (m.skips?.length) {
|
|
201
201
|
lines.push(`- Свідомо пропускає шляхи: ${m.skips.map(s => '`' + s + '`').join(', ')}.`)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** @see ./docs/docgen-scan.md */
|
|
2
|
-
|
|
3
|
-
import path from 'node:path'
|
|
2
|
+
import { join, dirname, basename, extname, relative, resolve, sep, isAbsolute, posix } from 'node:path'
|
|
4
3
|
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
5
4
|
import { execFileSync } from 'node:child_process'
|
|
6
5
|
import { once } from 'node:events'
|
|
@@ -27,7 +26,7 @@ const DEFAULT_GATE_MAX = Number(env.N_CURSOR_DOC_FILES_GATE_MAX ?? 50) || 50
|
|
|
27
26
|
* @returns {boolean} true — корінь system-wide docs
|
|
28
27
|
*/
|
|
29
28
|
function isSystemWideDocsRoot(root) {
|
|
30
|
-
return existsSync(
|
|
29
|
+
return existsSync(join(root, 'docs', 'adr')) || existsSync(join(root, 'docs', 'explanation'))
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
/**
|
|
@@ -38,7 +37,7 @@ function isSystemWideDocsRoot(root) {
|
|
|
38
37
|
export function isSourceFile(fileName) {
|
|
39
38
|
if (fileName.endsWith('.d.ts')) return false
|
|
40
39
|
if (TEST_FILE_RE.test(fileName)) return false
|
|
41
|
-
return SOURCE_EXTENSIONS.has(
|
|
40
|
+
return SOURCE_EXTENSIONS.has(extname(fileName))
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/**
|
|
@@ -48,9 +47,9 @@ export function isSourceFile(fileName) {
|
|
|
48
47
|
* @returns {string} шлях до `<dir>/docs/<stem>.md` у тому ж просторі шляхів
|
|
49
48
|
*/
|
|
50
49
|
export function docPathForSource(sourcePath) {
|
|
51
|
-
const dir =
|
|
52
|
-
const stem =
|
|
53
|
-
return
|
|
50
|
+
const dir = dirname(sourcePath)
|
|
51
|
+
const stem = basename(sourcePath, extname(sourcePath))
|
|
52
|
+
return join(dir, 'docs', `${stem}.md`)
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
/**
|
|
@@ -61,9 +60,9 @@ export function docPathForSource(sourcePath) {
|
|
|
61
60
|
* @returns {boolean} true — кандидат на доку
|
|
62
61
|
*/
|
|
63
62
|
export function isDocCandidate(root, relPath) {
|
|
64
|
-
const fileName =
|
|
63
|
+
const fileName = posix.basename(relPath)
|
|
65
64
|
if (!isSourceFile(fileName)) return false
|
|
66
|
-
if (isSystemWideDocsRoot(root) &&
|
|
65
|
+
if (isSystemWideDocsRoot(root) && posix.dirname(relPath) === '.') return false
|
|
67
66
|
return !isDocgenIgnored(relPath)
|
|
68
67
|
}
|
|
69
68
|
|
|
@@ -75,7 +74,7 @@ export function isDocCandidate(root, relPath) {
|
|
|
75
74
|
*/
|
|
76
75
|
export function describeFile(root, sourcePath) {
|
|
77
76
|
const docPath = docPathForSource(sourcePath)
|
|
78
|
-
const { stale, reason } = staleness(
|
|
77
|
+
const { stale, reason } = staleness(join(root, sourcePath), join(root, docPath))
|
|
79
78
|
return { sourcePath, docPath, stale, reason }
|
|
80
79
|
}
|
|
81
80
|
|
|
@@ -97,14 +96,14 @@ export function scanForDocFiles(root) {
|
|
|
97
96
|
return
|
|
98
97
|
}
|
|
99
98
|
for (const entry of entries) {
|
|
100
|
-
const fullPath =
|
|
101
|
-
const relPath =
|
|
99
|
+
const fullPath = join(dir, entry.name)
|
|
100
|
+
const relPath = relative(root, fullPath)
|
|
102
101
|
if (entry.isDirectory()) {
|
|
103
102
|
if (isDocgenIgnored(relPath, 'dir')) continue
|
|
104
103
|
walk(fullPath)
|
|
105
104
|
} else if (entry.isFile() && isSourceFile(entry.name)) {
|
|
106
|
-
if (isSystemWideDocsRoot(root) &&
|
|
107
|
-
const sourcePath = relPath.split(
|
|
105
|
+
if (isSystemWideDocsRoot(root) && dirname(relPath) === '.') continue
|
|
106
|
+
const sourcePath = relPath.split(sep).join('/')
|
|
108
107
|
if (isDocgenIgnored(sourcePath)) continue
|
|
109
108
|
results.push(describeFile(root, sourcePath))
|
|
110
109
|
}
|
|
@@ -122,7 +121,7 @@ export function scanForDocFiles(root) {
|
|
|
122
121
|
*/
|
|
123
122
|
export function resolveRoot(argv) {
|
|
124
123
|
const i = argv.indexOf('--root')
|
|
125
|
-
return i !== -1 && argv[i + 1] ?
|
|
124
|
+
return i !== -1 && argv[i + 1] ? resolve(argv[i + 1]) : process.cwd()
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
/**
|
|
@@ -190,7 +189,7 @@ function gitChangedSources(root) {
|
|
|
190
189
|
return out
|
|
191
190
|
.split('\n')
|
|
192
191
|
.map(s => s.trim())
|
|
193
|
-
.filter(rel => rel && isDocCandidate(root, rel) && existsSync(
|
|
192
|
+
.filter(rel => rel && isDocCandidate(root, rel) && existsSync(join(root, rel)))
|
|
194
193
|
}
|
|
195
194
|
|
|
196
195
|
/**
|
|
@@ -200,9 +199,9 @@ function gitChangedSources(root) {
|
|
|
200
199
|
* @returns {string|null} posix-шлях від кореня
|
|
201
200
|
*/
|
|
202
201
|
function toRelSource(root, candidate) {
|
|
203
|
-
const rel =
|
|
204
|
-
if (rel.startsWith('..') ||
|
|
205
|
-
return rel.split(
|
|
202
|
+
const rel = relative(root, resolve(root, candidate))
|
|
203
|
+
if (rel.startsWith('..') || isAbsolute(rel)) return null
|
|
204
|
+
return rel.split(sep).join('/')
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
/**
|
|
@@ -216,7 +215,7 @@ function runDegradedReport(root) {
|
|
|
216
215
|
const degraded = []
|
|
217
216
|
for (const f of scanForDocFiles(root)) {
|
|
218
217
|
if (f.stale) continue
|
|
219
|
-
const { score, issues } = readDocQuality(
|
|
218
|
+
const { score, issues } = readDocQuality(join(root, f.docPath))
|
|
220
219
|
if (score !== null && score < QUALITY_THRESHOLD) degraded.push({ ...f, score, issues })
|
|
221
220
|
}
|
|
222
221
|
if (degraded.length === 0) {
|
|
@@ -262,14 +261,14 @@ export async function runDocFilesCheckCli(argv) {
|
|
|
262
261
|
if (hookMode) {
|
|
263
262
|
const fp = extractHookFilePath(await readStdin())
|
|
264
263
|
const rel = fp ? toRelSource(root, fp) : null
|
|
265
|
-
sources = rel && isDocCandidate(root, rel) && existsSync(
|
|
264
|
+
sources = rel && isDocCandidate(root, rel) && existsSync(join(root, rel)) ? [rel] : []
|
|
266
265
|
} else if (gitMode) {
|
|
267
266
|
sources = gitChangedSources(root)
|
|
268
267
|
} else {
|
|
269
268
|
sources = argv
|
|
270
269
|
.filter(a => !a.startsWith('--') && a !== argv[maxIdx + 1])
|
|
271
270
|
.map(a => toRelSource(root, a))
|
|
272
|
-
.filter(rel => rel && isDocCandidate(root, rel) && existsSync(
|
|
271
|
+
.filter(rel => rel && isDocCandidate(root, rel) && existsSync(join(root, rel)))
|
|
273
272
|
}
|
|
274
273
|
|
|
275
274
|
const stale = sources.map(src => describeFile(root, src)).filter(f => f.stale)
|
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
---
|
|
2
2
|
docgen:
|
|
3
3
|
source: npm/skills/doc-files/js/docgen-extract-anchors.mjs
|
|
4
|
-
crc:
|
|
4
|
+
crc: 49749e9c
|
|
5
|
+
score: 80
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
# docgen-extract-anchors
|
|
8
|
+
# docgen-extract-anchors.mjs
|
|
8
9
|
|
|
9
10
|
## Огляд
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Витягує анкори з вихідного коду файлу.
|
|
13
|
+
|
|
14
|
+
Витягує анкори з вихідного коду файлу.
|
|
15
|
+
|
|
16
|
+
Формує плоский список анкор-токенів для перевірки покриття.
|
|
17
|
+
|
|
18
|
+
Форматує анкори у компактний текст для system-промпта.
|
|
12
19
|
|
|
13
20
|
## Поведінка
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
X
|
|
23
|
+
Витягує анкори з вихідного коду файлу
|
|
24
|
+
|
|
25
|
+
extractAnchors
|
|
26
|
+
Витягує анкори з вихідного коду файлу
|
|
27
|
+
|
|
28
|
+
anchorTokens
|
|
29
|
+
Формує плоский список анкор-токенів для перевірки покриття
|
|
30
|
+
|
|
31
|
+
anchorsToPrompt
|
|
32
|
+
Форматує анкори у компактний текст для system-промпта
|
|
18
33
|
|
|
19
34
|
## Публічний API
|
|
20
35
|
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
X — витягує анкори з вихідного коду файлу.
|
|
37
|
+
extractAnchors — витягує анкори з вихідного коду файлу.
|
|
38
|
+
anchorTokens — плоский список анкор-токенів, які мають з'явитися в документі.
|
|
39
|
+
anchorsToPrompt — форматує анкори у компактний текст для system-промпта.
|
|
23
40
|
|
|
24
41
|
## Гарантії поведінки
|
|
25
42
|
|
|
26
|
-
-
|
|
27
|
-
-
|
|
43
|
+
- Read-only: файл не виконує операцій запису у файлову систему.
|
|
44
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
45
|
+
- Не звертається до мережі.
|
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
docgen:
|
|
3
3
|
source: npm/skills/doc-files/js/docgen-extract.mjs
|
|
4
|
-
crc:
|
|
4
|
+
crc: 1592076b
|
|
5
|
+
score: 100
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
# docgen-extract
|
|
8
|
+
# docgen-extract.mjs
|
|
8
9
|
|
|
9
10
|
## Огляд
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Файл витягує факти про конфігурацію та структуру проекту. Визначає мову та заголовок файлу. Витягує публічні експорти та імпорти з різних джерел. Витягує внутрішні та локальні символи. Документація перевіряє наявність операцій запису, обробки помилок, повернення хибних значень при невдачах, мережевих викликів, механізмів кешування та пропуск певних шляхів.
|
|
12
13
|
|
|
13
14
|
## Поведінка
|
|
14
15
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
16
|
+
1. Витягується факт про файл.
|
|
17
|
+
2. Визначається мова файлу.
|
|
18
|
+
3. Витягується заголовок файлу.
|
|
19
|
+
4. Витягуються публічні експорти.
|
|
20
|
+
5. Витягуються імпорти, класифіковані за джерелом.
|
|
21
|
+
6. Витягуються внутрішні символи, імпортовані з внутрішніх модулів.
|
|
22
|
+
7. Витягуються локальні символи, неекспортовані функції.
|
|
23
|
+
8. Визначаються евристичні маркери.
|
|
24
|
+
* readOnly: перевірка на наявність операцій запису.
|
|
25
|
+
* catchesErrors: перевірка на наявність обробки помилок.
|
|
26
|
+
* returnsFalsyOnFail: перевірка на повернення хибних значень при невдачах.
|
|
27
|
+
* network: перевірка на наявність мережевих викликів.
|
|
28
|
+
* caches: перевірка на наявність механізмів кешування.
|
|
29
|
+
* skips: пропуск шляхів .github, .git, node_modules, base/, ua/, .firebase.
|
|
20
30
|
|
|
21
31
|
## Публічний API
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
extractFacts — код файлу перетворює на список фактів
|
|
24
34
|
|
|
25
35
|
## Гарантії поведінки
|
|
26
36
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
37
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
38
|
+
- Кешує результати в межах одного прогону.
|
|
39
|
+
- Свідомо пропускає шляхи: `.github`, `.git`, `node_modules`, `base/`, `ua/`, `.firebase`.
|
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
docgen:
|
|
3
3
|
source: npm/skills/doc-files/js/docgen-files-batch.mjs
|
|
4
|
-
crc:
|
|
4
|
+
crc: 5c9b8d72
|
|
5
|
+
score: 95
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
# docgen-files-batch
|
|
8
|
+
# docgen-files-batch.mjs
|
|
8
9
|
|
|
9
10
|
## Огляд
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
runDocFilesGenCli
|
|
13
|
+
Запускає генерацію документації для застарілих або відсутніх файлів.
|
|
14
|
+
|
|
15
|
+
runDocFilesStampCli
|
|
16
|
+
Перештампує frontmatter джерело та CRC у наявних документах без виклику LLM.
|
|
12
17
|
|
|
13
18
|
## Поведінка
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
runDocFilesGenCli
|
|
21
|
+
Запускає генерацію документації для застарілих/відсутніх док.
|
|
22
|
+
|
|
23
|
+
runDocFilesStampCli
|
|
24
|
+
Перештампує frontmatter source+crc у наявних доках без виклику LLM.
|
|
25
|
+
|
|
26
|
+
## Публічний API
|
|
27
|
+
|
|
28
|
+
- runDocFilesGenCli — згенерувати документацію для застарілих/відсутніх док.
|
|
29
|
+
- runDocFilesStampCli — детерміновано (пере)штампувати frontmatter `source`+`crc` у наявних доках без виклику LLM. Для міграції док, які ще не мають CRC. Поля якості (`score`/`issues`) зберігаються з наявного frontmatter.
|
|
20
30
|
|
|
21
31
|
## Гарантії поведінки
|
|
22
32
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
33
|
+
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
34
|
+
- За невдачі повертає значення помилки (`false`/`null`/`Err`) замість генерування винятку чи паніки.
|
|
35
|
+
- Не звертається до мережі.
|