@7n/rules 1.49.26 → 1.50.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 +10 -0
- package/package.json +1 -1
- package/rules/doc-files/check/docs/index.md +10 -0
- package/rules/doc-files/check/docs/main.md +4 -2
- package/rules/doc-files/check/main.mjs +12 -4
- package/rules/doc-files/docgen-crc/docs/index.md +9 -0
- package/rules/doc-files/docgen-crc/docs/main.md +47 -26
- package/rules/doc-files/docgen-crc/main.mjs +21 -4
- package/rules/doc-files/docgen-files-batch/docs/main.md +20 -14
- package/rules/doc-files/docgen-files-batch/main.mjs +54 -20
- package/rules/doc-files/docgen-gen/docs/main.md +35 -3
- package/rules/doc-files/docgen-gen/main.mjs +253 -19
- package/rules/doc-files/docgen-prompts/docs/main.md +18 -12
- package/rules/doc-files/docgen-prompts/main.mjs +19 -12
- package/rules/doc-files/docgen-scan/docs/main.md +44 -32
- package/rules/doc-files/docgen-scan/main.mjs +6 -3
- package/rules/doc-files/docgen-test-context/docs/index.md +9 -0
- package/rules/doc-files/docgen-test-context/docs/main.md +57 -0
- package/rules/doc-files/docgen-test-context/main.mjs +212 -0
- package/rules/doc-files/main.mdc +37 -6
- package/rules/k8s/manifests/main.mjs +94 -38
- package/scripts/lib/lint-surface/lint-lock.mjs +81 -16
- package/scripts/lib/lint-surface/progress.mjs +15 -3
- package/scripts/lib/lint-surface/run-detectors.mjs +9 -1
- package/scripts/lib/lint-surface/types.mjs +2 -0
- package/skills/doc-files/SKILL.md +21 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.50.0] - 2026-07-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- doc-files без LLM рендерить test-сценарії та повністю прокоментовані JS, Rust і Python файли, а для коротких comments додає лише відсутню «Поведінку»
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Прискорено k8s full lint і додано live статус черги
|
|
12
|
+
|
|
3
13
|
## [1.49.26] - 2026-07-27
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/package.json
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Directory Index
|
|
3
|
+
title: npm/rules/doc-files/check
|
|
4
|
+
resource: npm/rules/doc-files/check/
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
| Файл | Тип |
|
|
8
|
+
| ------------------------------- | --------- |
|
|
9
|
+
| [fix-worker.mjs](fix-worker.md) | JS Module |
|
|
10
|
+
| [main.mjs](main.md) | JS Module |
|
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/doc-files/check/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: d13133e6
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
## Огляд
|
|
@@ -16,6 +16,8 @@ Lint-детектор doc-files: знаходить застарілі файл
|
|
|
16
16
|
|
|
17
17
|
Реверс-мапінг: якщо серед змінених файлів трапляється сама `.md`-дока (а не її джерело), `sourceForDoc` шукає відповідний вихідний файл поруч (той самий basename, легальне розширення) і саме його передає далі в перевірку застарілості — так зміна доки теж тригерить звірку CRC її джерела.
|
|
18
18
|
|
|
19
|
+
Якщо змінено test/spec-файл, `sourceFilesForTest` через спільний source↔tests index знаходить лише пов’язані source-файли. Їхня документація також перевіряється на застарілість, тому зміна підтвердженого usage-сценарію не лишає docs зі старим CRC.
|
|
20
|
+
|
|
19
21
|
Якщо мапа doc-files-розширень від активних плагінів порожня, а хоча б один плагін явно задекларований у `.n-rules.json`, але не встановлений у `node_modules` — детектор додає `diagnostics`-запис (`level: 'warn'`) з підказкою запустити `bun install`. Це відрізняє "плагін не встановлено" від "усі доки актуальні": без нього обидва випадки виглядають як 0 порушень.
|
|
20
22
|
|
|
21
23
|
## Публічний API
|
|
@@ -25,5 +27,5 @@ Lint-детектор doc-files: знаходить застарілі файл
|
|
|
25
27
|
## Гарантії поведінки
|
|
26
28
|
|
|
27
29
|
- Read-only: не пише і не видаляє жодних файлів.
|
|
28
|
-
-
|
|
30
|
+
- Містить локальні fail-safe гілки для недоступних тек; інші помилки можуть поширюватися назовні.
|
|
29
31
|
- Diagnostics-попередження про невстановлений плагін рендериться лише при `--verbose` на explicit CLI-виклику (`lint doc-files --no-fix --verbose`) — у PostToolUse-хуку (без `verbose`) обчислюється, але ніколи не друкується.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lint-поверхня doc-files: детект застарілих файлових документацій (per-file, з reverse-mapом).
|
|
3
3
|
*/
|
|
4
|
-
import { join, dirname, basename, extname } from 'node:path'
|
|
4
|
+
import { join, dirname, basename, extname, relative } from 'node:path'
|
|
5
5
|
import { existsSync, readdirSync } from 'node:fs'
|
|
6
6
|
|
|
7
7
|
import { describeFile, isDocCandidate, isSourceFile, scanForDocFiles, scanOrphanedDocs } from '../docgen-scan/main.mjs'
|
|
8
8
|
import { unavailableDocFilesPlugins } from '../docgen-scan/lang-extensions.mjs'
|
|
9
|
+
import { buildTestEvidenceIndex, isDocgenTestFile, sourceFilesForTest } from '../docgen-test-context/main.mjs'
|
|
9
10
|
|
|
10
11
|
const DOC_MD_RE = /(?:^|\/)docs\/[^/]+\.md$/u
|
|
11
12
|
|
|
@@ -38,15 +39,21 @@ function sourceForDoc(cwd, docRel) {
|
|
|
38
39
|
* Зводить перелік змінених файлів до множини вихідних кодових файлів.
|
|
39
40
|
* @param {string[]} files змінені шляхи (джерела або .md-доки)
|
|
40
41
|
* @param {string} cwd робочий каталог
|
|
42
|
+
* @param {ReturnType<typeof buildTestEvidenceIndex>} testIndex source↔tests index
|
|
41
43
|
* @returns {string[]} відносні шляхи джерел
|
|
42
44
|
*/
|
|
43
|
-
function sourcesFromChanged(files, cwd) {
|
|
45
|
+
function sourcesFromChanged(files, cwd, testIndex) {
|
|
44
46
|
const out = new Set()
|
|
45
47
|
for (const raw of files) {
|
|
46
48
|
const rel = raw.split('\\').join('/')
|
|
47
49
|
if (DOC_MD_RE.test(rel)) {
|
|
48
50
|
const src = sourceForDoc(cwd, rel)
|
|
49
51
|
if (src) out.add(src)
|
|
52
|
+
} else if (isDocgenTestFile(basename(rel))) {
|
|
53
|
+
for (const sourceAbs of sourceFilesForTest(join(cwd, rel), testIndex)) {
|
|
54
|
+
const sourceRel = relative(cwd, sourceAbs).split('\\').join('/')
|
|
55
|
+
if (isDocCandidate(cwd, sourceRel)) out.add(sourceRel)
|
|
56
|
+
}
|
|
50
57
|
} else if (isDocCandidate(cwd, rel) && existsSync(join(cwd, rel))) {
|
|
51
58
|
out.add(rel)
|
|
52
59
|
}
|
|
@@ -61,8 +68,9 @@ function sourcesFromChanged(files, cwd) {
|
|
|
61
68
|
*/
|
|
62
69
|
export function collectStale(files, cwd) {
|
|
63
70
|
if (files === undefined) return scanForDocFiles(cwd).filter(f => f.stale)
|
|
64
|
-
const
|
|
65
|
-
|
|
71
|
+
const testIndex = buildTestEvidenceIndex(cwd)
|
|
72
|
+
const sources = sourcesFromChanged(files, cwd, testIndex)
|
|
73
|
+
return sources.map(src => describeFile(cwd, src, testIndex)).filter(f => f.stale)
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
/**
|
|
@@ -3,44 +3,65 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/doc-files/docgen-crc/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model:
|
|
6
|
+
crc: fdddffc3
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
8
9
|
score: 100
|
|
9
|
-
issues: judge:
|
|
10
|
+
issues: judge:error
|
|
10
11
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
11
12
|
---
|
|
12
13
|
|
|
13
14
|
## Огляд
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
`crc32` обчислює детермінований 8-символьний hex CRC для рядка або Buffer; `documentationCrc` і `parseDocFrontmatter` зв’язують вміст документа з його frontmatter. `buildDocFrontmatter` і `stampDoc` записують fresh metadata для `CRC`, `model`, `tier` та `quality`, причому `stampDoc` ще й керує маркером `degraded`. `readDocCrc`, `readDocModel`, `readDocTier` і `readDocQuality` відновлюють ці значення з доки, а `QUALITY_THRESHOLD` задає дефолтний поріг 70. `staleness` порівнює source і doc, щоб відрізняти `missing`, `crc-mismatch` і `fresh`.
|
|
16
17
|
|
|
17
18
|
## Поведінка
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
crc32
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
QUALITY_THRESHOLD задає дефолтний поріг оцінки якості для читання й маркування доки; у перевірених сценаріях цей поріг дорівнює 70.
|
|
21
|
+
|
|
22
|
+
crc32 дає детермінований 8-символьний hex для рядка або Buffer; однаковий вміст завжди дає той самий CRC, а різний — інший; відомий вектор `123456789` зводиться до `cbf43926`.
|
|
23
|
+
|
|
24
|
+
documentationCrc обчислює CRC для поведінкової документації не лише з джерела, а й з пов’язаного evidence тестів, якщо він є; без пов’язаних тестів лишається сумісним із CRC самого source, тож зміна лише сценарію використання робить доку застарілою.
|
|
25
|
+
|
|
26
|
+
parseDocFrontmatter відокремлює frontmatter від тіла доки й повертає нормалізовані метадані; якщо frontmatter немає, тіло лишається без змін, а метадані відсутні; старі доки без частини полів читаються як сумісні: відсутні `model`, `tier`, `score`, `issues` та `judgeModel` стають порожніми значеннями.
|
|
27
|
+
|
|
28
|
+
buildDocFrontmatter формує frontmatter так, щоб спочатку були OKF-поля джерела, а потім вкладений блок якості та генератора; `model` і `tier` додаються лише коли вони є, а `issues` скорочуються до YAML-безпечних кодів і мають обмеження на кількість; quality може співіснувати з model, причому score та issues зберігаються й читаються назад без втрат.
|
|
29
|
+
|
|
30
|
+
stampDoc переоформлює існуючу MD-доку: знімає старий frontmatter і додає свіжий, не змінюючи тіло; коли quality є, у frontmatter лишається degraded-сигнал разом із score/issues, а коли quality зникає — цей стан теж знімається; `model` переноситься у новий frontmatter разом із рештою актуальних метаданих.
|
|
31
|
+
|
|
32
|
+
readDocCrc повертає CRC, уже записаний у frontmatter; якщо доки немає або CRC не зафіксований, результат `null`.
|
|
33
|
+
|
|
34
|
+
readDocQuality читає збережену оцінку доки; за відсутності доки або score повертає `score: null`, порожній список issues і `judgeModel: null`; коли якість записана, значення відновлюються назад без втрат.
|
|
35
|
+
|
|
36
|
+
readDocModel повертає збережену модель генератора або `null`, якщо доки немає чи поле не записане.
|
|
37
|
+
|
|
38
|
+
readDocTier повертає tier моделі генератора або `null`, якщо доки немає чи поле не записане.
|
|
39
|
+
|
|
40
|
+
staleness порівнює evidence source з CRC, записаним у відповідній доці: коли доки немає, стан `missing`; коли CRC не збігається, `crc-mismatch`; при збігу дока свіжа; для пов’язаних тестів у evidence враховується й їхній вплив на CRC доки, тому зміна сценарію використання може зробити документацію stale навіть без зміни source.
|
|
30
41
|
|
|
31
42
|
## Публічний API
|
|
32
43
|
|
|
33
|
-
QUALITY_THRESHOLD —
|
|
34
|
-
crc32 —
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
- QUALITY_THRESHOLD — Поріг degraded: дока зі `score` нижче вважається неякісною.
|
|
45
|
+
- crc32 — CRC32 вмісту у hex (8 символів, з провідними нулями). Делегує у нативний
|
|
46
|
+
`node:zlib.crc32` — без ручної бітової арифметики.
|
|
47
|
+
- documentationCrc — CRC повного evidence для файлової доки. Без повʼязаних тестів лишається
|
|
48
|
+
back-compatible CRC самого source; за наявності тестів додає їхні шляхи та
|
|
49
|
+
вміст, тому зміна usage-сценарію детерміновано робить доку stale.
|
|
50
|
+
- parseDocFrontmatter — Парсить frontmatter файлової доки. Без блоку — `data:null` і `body` дорівнює входу.
|
|
51
|
+
Поля `model`/`score`/`issues` опційні (back-compat зі старими доками): без них —
|
|
52
|
+
`model:null`, `score:null`, `issues:[]`.
|
|
53
|
+
- buildDocFrontmatter — Будує OKF-сумісний frontmatter-блок: OKF-поля верхнього рівня + вкладений `docgen:`
|
|
54
|
+
з CRC/model/quality. OKF-поля виводяться першими, щоб будь-який OKF-парсер міг їх
|
|
55
|
+
читати незалежно від `docgen:`-простору назв.
|
|
56
|
+
- stampDoc — (Пере)штампує frontmatter у md-доку: знімає наявний блок і додає свіжий.
|
|
57
|
+
- readDocCrc — CRC, збережений у frontmatter доки; `null` — доки немає або CRC відсутній.
|
|
58
|
+
- readDocQuality — Якість, збережена у frontmatter доки.
|
|
59
|
+
- readDocModel — Модель-генератор, збережена у frontmatter доки; `null` — доки немає або поле відсутнє
|
|
60
|
+
(старі доки до введення `model`).
|
|
61
|
+
- readDocTier — Tier моделі-генератора зі frontmatter доки; `null` — доки немає або поле відсутнє.
|
|
62
|
+
- staleness — Стан застарілості доки відносно evidence: source + повʼязані тести.
|
|
63
|
+
`missing` — доки немає; `crc-mismatch` — evidence CRC ≠ CRC у доці; інакше свіжа.
|
|
43
64
|
|
|
44
65
|
## Гарантії поведінки
|
|
45
66
|
|
|
46
|
-
-
|
|
67
|
+
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|
|
@@ -4,6 +4,7 @@ import { basename, extname } from 'node:path'
|
|
|
4
4
|
import { crc32 as zlibCrc32 } from 'node:zlib'
|
|
5
5
|
import { env } from 'node:process'
|
|
6
6
|
import { pluginDocFilesExtensions } from '../docgen-scan/lang-extensions.mjs'
|
|
7
|
+
import { testEvidenceForSource } from '../docgen-test-context/main.mjs'
|
|
7
8
|
|
|
8
9
|
/** Поріг degraded: дока зі `score` нижче вважається неякісною. */
|
|
9
10
|
export const QUALITY_THRESHOLD = Number(env.N_CURSOR_DOC_FILES_THRESHOLD ?? 70) || 70
|
|
@@ -19,6 +20,21 @@ export function crc32(input) {
|
|
|
19
20
|
return zlibCrc32(buf).toString(16).padStart(8, '0')
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* CRC повного evidence для файлової доки. Без повʼязаних тестів лишається
|
|
25
|
+
* back-compatible CRC самого source; за наявності тестів додає їхні шляхи та
|
|
26
|
+
* вміст, тому зміна usage-сценарію детерміновано робить доку stale.
|
|
27
|
+
* @param {string} sourceAbsPath абсолютний шлях source-файлу
|
|
28
|
+
* @param {ReturnType<import('../docgen-test-context/main.mjs').buildTestEvidenceIndex>|null} [testIndex] source↔tests index
|
|
29
|
+
* @returns {string} CRC32 source + повʼязаних тестів
|
|
30
|
+
*/
|
|
31
|
+
export function documentationCrc(sourceAbsPath, testIndex = null) {
|
|
32
|
+
const source = readFileSync(sourceAbsPath)
|
|
33
|
+
if (!testIndex) return crc32(source)
|
|
34
|
+
const { crcPayload } = testEvidenceForSource(sourceAbsPath, testIndex)
|
|
35
|
+
return crc32(crcPayload ? Buffer.concat([source, Buffer.from(crcPayload, 'utf8')]) : source)
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
/** Провідний YAML-frontmatter-блок `---\n…\n---`. */
|
|
23
39
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/u
|
|
24
40
|
const RESOURCE_RE = /^resource:[ \t]+(\S.*)$/mu
|
|
@@ -186,16 +202,17 @@ export function readDocTier(docAbsPath) {
|
|
|
186
202
|
}
|
|
187
203
|
|
|
188
204
|
/**
|
|
189
|
-
* Стан застарілості доки відносно
|
|
190
|
-
* `missing` — доки немає; `crc-mismatch` — CRC
|
|
205
|
+
* Стан застарілості доки відносно evidence: source + повʼязані тести.
|
|
206
|
+
* `missing` — доки немає; `crc-mismatch` — evidence CRC ≠ CRC у доці; інакше свіжа.
|
|
191
207
|
* @param {string} sourceAbsPath абсолютний шлях джерела
|
|
192
208
|
* @param {string} docAbsPath абсолютний шлях md-доки
|
|
209
|
+
* @param {ReturnType<import('../docgen-test-context/main.mjs').buildTestEvidenceIndex>|null} [testIndex] source↔tests index
|
|
193
210
|
* @returns {{ stale: boolean, reason: 'missing'|'crc-mismatch'|null }} стан застарілості
|
|
194
211
|
*/
|
|
195
|
-
export function staleness(sourceAbsPath, docAbsPath) {
|
|
212
|
+
export function staleness(sourceAbsPath, docAbsPath, testIndex = null) {
|
|
196
213
|
const docCrc = readDocCrc(docAbsPath)
|
|
197
214
|
if (docCrc === null) return { stale: true, reason: 'missing' }
|
|
198
|
-
const srcCrc =
|
|
215
|
+
const srcCrc = documentationCrc(sourceAbsPath, testIndex)
|
|
199
216
|
if (srcCrc !== docCrc) return { stale: true, reason: 'crc-mismatch' }
|
|
200
217
|
return { stale: false, reason: null }
|
|
201
218
|
}
|
|
@@ -3,33 +3,33 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/doc-files/docgen-files-batch/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model: openai-codex/gpt-5.
|
|
8
|
-
tier: cloud-
|
|
9
|
-
score:
|
|
10
|
-
issues: internal-name:generateOne,internal-name:runBatchPass,judge-refine:kept-original,judge:inaccurate:0.99
|
|
6
|
+
crc: 309bc059
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
|
+
score: 75
|
|
10
|
+
issues: internal-name:generateOne,internal-name:runBatchPass,anchor-miss:http://127.0.0.1:8000/v1/,judge-refine:kept-original,judge:inaccurate:0.99
|
|
11
11
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Огляд
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
Модуль звертається до мережі, але працює fail-safe: перехоплює помилки, не кидає винятків назовні і дає прогону завершитися контрольовано.
|
|
16
|
+
Визначає цілі для оновлення, генерує й синхронізує docs/ з кодом, оновлює directory index і прибирає orphaned docs, які більше не прив’язані до актуальних джерел. Працює як fail-safe: мережеві звернення не виносять винятки назовні, а результати прогону кешуються в межах одного запуску.
|
|
19
17
|
|
|
20
18
|
## Поведінка
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
Генерація документації стартує з виявлення цілей через selectTargets: у звичайному режимі беруться застарілі або degraded-доки, які ще не отримували повторної спроби для поточної версії джерела; у режимі overwrite обробляються всі. Це робить прогін сходинковим: після невдалої спроби degraded-док більше не чіпається, доки не зміниться джерело, а новий CRC автоматично повертає його в потік.
|
|
21
|
+
|
|
22
|
+
runDocFilesGenCli збирає підсумковий сценарій: спершу прибирає сирітські доки, потім запускає генерацію для відібраних цілей, а в кінці оновлює індекс директорії. Якщо прогін зупиняється достроково або через помилки, зроблене лишається на диску з актуальними CRC, тому наступний запуск підхоплює тільки пропущене.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
runGenerationBatch є спільним ядром усієї генерації. Воно бере відібрані цілі, робить preflight для локального бекенда і далі вибирає між послідовним шляхом та batch-шляхом. Якщо доступний native batch-аддон і немає м’якого дедлайну, весь набір іде одним submitBatch, і результати розкладаються назад по файлах. Якщо batch-режим недоступний або примусово вимкнений, обробка йде по одному файлу з fail-safe обробкою помилок та circuit-breaker для системних збоїв підряд. М’який дедлайн підтримується лише на послідовному шляху: перший файл завжди стартує, а наступні зупиняються, коли час вичерпано. Усі стани та лічильники накопичуються в спільній статистиці, а вихідний код відображає лише підсумок прогону.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
nativeBatchAvailable використовується як перемикач між batch і fallback-потоком. Перевірка не виконує LLM-виклику, а лише підтверджує, що native-реалізація доступна; результат кешується в межах прогону, щоб не повторювати однакову перевірку.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
generateDirIndex підтримує актуальний directory index у docs/ після будь-яких змін у наборах доків. Він читає наявні markdown-файли, витягує frontmatter і будує оглядову таблицю лише для реальних документів; сам index.md не чіпається, якщо в директорії більше нічого немає.
|
|
29
29
|
|
|
30
|
-
purgeOrphanedDocs
|
|
30
|
+
purgeOrphanedDocs прибирає доки, для яких уже немає source-файлів, і після цього синхронізує індекс директорії. Це тримає docs/ у стані, де в ньому лишається тільки те, що ще прив’язане до коду, а порожні директорії очищуються до мінімально можливого стану.
|
|
31
31
|
|
|
32
|
-
runDocFilesStampCli
|
|
32
|
+
runDocFilesStampCli працює окремо від генерації: він детерміновано оновлює frontmatter у вже наявних доках без звернення до LLM. Це корисно для міграції та для відновлення метаданих, коли треба синхронізувати source і crc без зміни тексту документа.
|
|
33
33
|
|
|
34
34
|
## Публічний API
|
|
35
35
|
|
|
@@ -73,6 +73,12 @@ T8 (2b-batch, рішення Р): коли доступний native-аддон
|
|
|
73
73
|
Поля `model`, `tier` та якості (`score`/`issues`/`judgeModel`) при цьому зберігаються
|
|
74
74
|
з наявного frontmatter.
|
|
75
75
|
|
|
76
|
+
## Сценарії використання
|
|
77
|
+
|
|
78
|
+
- `npm/rules/doc-files/docgen-files-batch/tests/docgen-files-batch.test.mjs` (runDocFilesGenCli — circuit-breaker / класифікація; selectTargets — stale + degraded-once guard) — 3 systemic підряд → abort, exit 2, решта не обробляється; permanent → skip, прогін триває, exit 0; ok між systemic скидає streak → без abort; default: stale | degraded-not-cloud-avg → обрано; good | degraded-cloud-avg → пропущено; --overwrite → усі цілі незалежно від стану; ще 14
|
|
79
|
+
- `npm/rules/doc-files/docgen-files-batch/tests/docgen-files-stamp.test.mjs` (runDocFilesStampCli — збереження frontmatter-полів) — stamp оновлює crc і НЕ губить tier/judgeModel/model/score/issues; stamp доки без quality-полів не вигадує їх і зберігає tier
|
|
80
|
+
- `npm/rules/doc-files/docgen-files-batch/tests/generate-dir-index.test.mjs` (generateDirIndex — MD025/single-title; generateDirIndex — чужий index.md не перезаписується) — згенерований index.md без H1 у тілі; markdownlint не репортить MD025; контроль чутливості: frontmatter title + H1 у тілі → markdownlint репортить MD025; людський index.md без frontmatter лишається недоторканим; index.md як дока source-файлу (type JS Module) лишається недоторканою; власний Directory Index перегенеровується; без інших док index не створюється
|
|
81
|
+
|
|
76
82
|
## Гарантії поведінки
|
|
77
83
|
|
|
78
84
|
- Перехоплює помилки і не пропускає винятків назовні (fail-safe).
|
|
@@ -16,8 +16,16 @@ import { env } from 'node:process'
|
|
|
16
16
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
17
17
|
import { createProgressReporter } from '../../../scripts/lib/lint-surface/progress.mjs'
|
|
18
18
|
import { generateDoc, DEFAULT_LOCAL_MODEL, prepareBatchItem, finishBatchItem } from '../docgen-gen/main.mjs'
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
documentationCrc,
|
|
21
|
+
stampDoc,
|
|
22
|
+
readDocQuality,
|
|
23
|
+
readDocModel,
|
|
24
|
+
readDocTier,
|
|
25
|
+
QUALITY_THRESHOLD
|
|
26
|
+
} from '../docgen-crc/main.mjs'
|
|
20
27
|
import { resolveRoot, scanForDocFiles, scanOrphanedDocs } from '../docgen-scan/main.mjs'
|
|
28
|
+
import { buildTestEvidenceIndex } from '../docgen-test-context/main.mjs'
|
|
21
29
|
import { submitBatch as submitBatchNative } from '@7n/llm-lib/batch'
|
|
22
30
|
|
|
23
31
|
/** Regex-класифікатори помилки генерації (module-scope, без ре-компіляції на виклик). */
|
|
@@ -147,10 +155,16 @@ function fmtSize(bytes) {
|
|
|
147
155
|
* @param {string} root абсолютний корінь
|
|
148
156
|
* @param {{ done: number, total: number }} progress позиція у прогресі
|
|
149
157
|
* @param {{ ok: number, degraded: number, err: number, errors: string[], skipped: string[] }} stats акумулятор
|
|
150
|
-
* @param {{ model?: string, tier?: string|null, emit?: (s: string) => void, deadlineAt?: number|null }} [opts] модель/тир для штампу; emit — логер рядка результату; deadlineAt — мʼякий дедлайн fix-pipeline для generateDoc
|
|
158
|
+
* @param {{ model?: string, tier?: string|null, emit?: (s: string) => void, deadlineAt?: number|null, testIndex?: ReturnType<typeof buildTestEvidenceIndex> }} [opts] модель/тир для штампу; emit — логер рядка результату; deadlineAt — мʼякий дедлайн fix-pipeline для generateDoc; testIndex — спільний source↔tests index
|
|
151
159
|
* @returns {Promise<'ok'|'permanent'|'systemic'|'transient'>} результат для керування циклом
|
|
152
160
|
*/
|
|
153
|
-
async function generateOne(
|
|
161
|
+
async function generateOne(
|
|
162
|
+
file,
|
|
163
|
+
root,
|
|
164
|
+
progress,
|
|
165
|
+
stats,
|
|
166
|
+
{ model, tier, emit, deadlineAt = null, testIndex = buildTestEvidenceIndex(root) } = {}
|
|
167
|
+
) {
|
|
154
168
|
const out = emit ?? (s => process.stdout.write(s))
|
|
155
169
|
const sourceAbs = join(root, file.sourcePath)
|
|
156
170
|
let size = 0
|
|
@@ -164,8 +178,8 @@ async function generateOne(file, root, progress, stats, { model, tier, emit, dea
|
|
|
164
178
|
const docAbs = join(root, file.docPath)
|
|
165
179
|
// Варіант B: передаємо наявну доку, щоб зберегти захищену секцію «Призначення»
|
|
166
180
|
const existingMd = existsSync(docAbs) ? readFileSync(docAbs, 'utf8') : null
|
|
167
|
-
const result = await generateDoc(sourceAbs, { existingMd, model, deadlineAt })
|
|
168
|
-
const crc =
|
|
181
|
+
const result = await generateDoc(sourceAbs, { existingMd, model, deadlineAt, testIndex })
|
|
182
|
+
const crc = documentationCrc(sourceAbs, testIndex)
|
|
169
183
|
mkdirSync(dirname(docAbs), { recursive: true })
|
|
170
184
|
const quality =
|
|
171
185
|
result.score === null
|
|
@@ -261,7 +275,7 @@ function defaultLocalProviders() {
|
|
|
261
275
|
* де м'який дедлайн підтримується).
|
|
262
276
|
* @param {Array<object>} targets елементи scanForDocFiles
|
|
263
277
|
* @param {string} root абсолютний корінь
|
|
264
|
-
* @param {{ model?: string, tier?: string|null, localProviders?: object, submitBatchImpl?: (modelSpecOrTier: string, items: Array<object>, opts?: object) => Promise<Array<object
|
|
278
|
+
* @param {{ model?: string, tier?: string|null, localProviders?: object, submitBatchImpl?: (modelSpecOrTier: string, items: Array<object>, opts?: object) => Promise<Array<object>>, testIndex: ReturnType<typeof buildTestEvidenceIndex> }} opts модель/тир/local-provider-конфіг (інакше `defaultLocalProviders()`)/інжект submitBatch/source↔tests index
|
|
265
279
|
* @param {{ ok: number, degraded: number, err: number, errors: string[], skipped: string[] }} stats акумулятор (мутується)
|
|
266
280
|
* @param {{ reporter?: object, emit?: (s: string) => void }} io прогрес-репортер і логер рядка результату
|
|
267
281
|
* @returns {Promise<void>}
|
|
@@ -271,20 +285,28 @@ async function runBatchPass(targets, root, opts, stats, { reporter, emit }) {
|
|
|
271
285
|
const submitBatchImpl = opts.submitBatchImpl ?? submitBatchNative
|
|
272
286
|
const out = emit ?? (s => process.stdout.write(s))
|
|
273
287
|
|
|
274
|
-
const prepared = await prepareBatchTargets(targets, root, stats, { reporter, out })
|
|
288
|
+
const prepared = await prepareBatchTargets(targets, root, stats, { reporter, out }, opts.testIndex)
|
|
275
289
|
if (prepared.length === 0) return
|
|
276
290
|
|
|
277
|
-
|
|
291
|
+
// Авторські comments уже є джерелом істини: штампуємо такі документи тут,
|
|
292
|
+
// не створюючи порожній native batch і не торкаючись LLM.
|
|
293
|
+
const llmPrepared = prepared.filter(p => p.mode !== 'comment-only')
|
|
294
|
+
for (const p of prepared.filter(p => p.mode === 'comment-only')) {
|
|
295
|
+
processBatchResult({ ok: '' }, p, { model, tier: opts.tier ?? null, stats, out })
|
|
296
|
+
}
|
|
297
|
+
if (llmPrepared.length === 0) return
|
|
298
|
+
|
|
299
|
+
const items = llmPrepared.map(p => ({
|
|
278
300
|
customId: p.file.sourcePath,
|
|
279
301
|
prompt: p.messages.find(m => m.role === 'user')?.content ?? '',
|
|
280
302
|
system: p.messages.find(m => m.role === 'system')?.content
|
|
281
303
|
}))
|
|
282
|
-
const onProgress = makeBatchProgress(reporter, targets.length -
|
|
304
|
+
const onProgress = makeBatchProgress(reporter, targets.length - llmPrepared.length, targets.length)
|
|
283
305
|
const localProviders = opts.localProviders ?? defaultLocalProviders()
|
|
284
306
|
const results = await submitBatchImpl(model, items, { onProgress, localProviders })
|
|
285
307
|
const byId = new Map(results.map(r => [r.customId, r]))
|
|
286
308
|
|
|
287
|
-
for (const p of
|
|
309
|
+
for (const p of llmPrepared) {
|
|
288
310
|
processBatchResult(byId.get(p.file.sourcePath), p, { model, tier: opts.tier ?? null, stats, out })
|
|
289
311
|
}
|
|
290
312
|
}
|
|
@@ -297,9 +319,10 @@ async function runBatchPass(targets, root, opts, stats, { reporter, emit }) {
|
|
|
297
319
|
* @param {string} root абсолютний корінь
|
|
298
320
|
* @param {{ ok: number, degraded: number, err: number, errors: string[], skipped: string[] }} stats акумулятор (мутується)
|
|
299
321
|
* @param {{ reporter?: object, out: (s: string) => void }} io прогрес-репортер і логер
|
|
300
|
-
* @
|
|
322
|
+
* @param {ReturnType<typeof buildTestEvidenceIndex>} testIndex source↔tests index
|
|
323
|
+
* @returns {Promise<Array<object>>} елементи, готові до batch-у або 0-LLM запису (file/sourceAbs/docAbs/size/facts/anchors/src/mode/messages/intent/testIndex)
|
|
301
324
|
*/
|
|
302
|
-
async function prepareBatchTargets(targets, root, stats, { reporter, out }) {
|
|
325
|
+
async function prepareBatchTargets(targets, root, stats, { reporter, out }, testIndex) {
|
|
303
326
|
const prepared = []
|
|
304
327
|
for (const file of targets) {
|
|
305
328
|
const sourceAbs = join(root, file.sourcePath)
|
|
@@ -312,8 +335,8 @@ async function prepareBatchTargets(targets, root, stats, { reporter, out }) {
|
|
|
312
335
|
}
|
|
313
336
|
const existingMd = existsSync(docAbs) ? readFileSync(docAbs, 'utf8') : null
|
|
314
337
|
try {
|
|
315
|
-
const prep = await prepareBatchItem(sourceAbs, { existingMd })
|
|
316
|
-
prepared.push({ file, sourceAbs, docAbs, size, ...prep })
|
|
338
|
+
const prep = await prepareBatchItem(sourceAbs, { existingMd, testIndex })
|
|
339
|
+
prepared.push({ file, sourceAbs, docAbs, size, testIndex, ...prep })
|
|
317
340
|
} catch (error) {
|
|
318
341
|
recordBatchOutcome(stats, out, file.sourcePath, size, error.message)
|
|
319
342
|
}
|
|
@@ -390,8 +413,15 @@ function processBatchResult(r, p, { model, tier, stats, out }) {
|
|
|
390
413
|
)
|
|
391
414
|
return
|
|
392
415
|
}
|
|
393
|
-
const finished = finishBatchItem(r.ok, {
|
|
394
|
-
|
|
416
|
+
const finished = finishBatchItem(r.ok, {
|
|
417
|
+
facts: p.facts,
|
|
418
|
+
anchors: p.anchors,
|
|
419
|
+
src: p.src,
|
|
420
|
+
intent: p.intent,
|
|
421
|
+
model,
|
|
422
|
+
mode: p.mode
|
|
423
|
+
})
|
|
424
|
+
const crc = documentationCrc(p.sourceAbs, p.testIndex)
|
|
395
425
|
mkdirSync(dirname(p.docAbs), { recursive: true })
|
|
396
426
|
const quality =
|
|
397
427
|
finished.score === null
|
|
@@ -632,7 +662,8 @@ async function runSequentialPass(targets, root, opts, stats, { reporter, emit })
|
|
|
632
662
|
model: opts.model,
|
|
633
663
|
tier: opts.tier,
|
|
634
664
|
emit,
|
|
635
|
-
deadlineAt: opts.deadlineAt ?? null
|
|
665
|
+
deadlineAt: opts.deadlineAt ?? null,
|
|
666
|
+
testIndex: opts.testIndex
|
|
636
667
|
})
|
|
637
668
|
reporter?.concernDone(file.sourcePath)
|
|
638
669
|
// Circuit-breaker: K systemic-збоїв підряд → негайний abort (середовище впало,
|
|
@@ -682,6 +713,8 @@ export async function runGenerationBatch(targets, root, opts = {}) {
|
|
|
682
713
|
|
|
683
714
|
if (headline) console.log(headline)
|
|
684
715
|
const stats = { ok: 0, degraded: 0, err: 0, errors: [], skipped: [] }
|
|
716
|
+
const testIndex = buildTestEvidenceIndex(root)
|
|
717
|
+
const runOpts = { ...opts, testIndex }
|
|
685
718
|
|
|
686
719
|
// ProgressReporter (канон scripts.mdc): бар по файлах лише в TTY; не-TTY лишає
|
|
687
720
|
// поточні append-рядки [done/total] без дубльованого ⏱-зведення.
|
|
@@ -706,10 +739,10 @@ export async function runGenerationBatch(targets, root, opts = {}) {
|
|
|
706
739
|
let deadlineHit = false
|
|
707
740
|
try {
|
|
708
741
|
if (useBatch) {
|
|
709
|
-
await runBatchPass(targets, root, { ...
|
|
742
|
+
await runBatchPass(targets, root, { ...runOpts, submitBatchImpl }, stats, { reporter, emit })
|
|
710
743
|
done = targets.length
|
|
711
744
|
} else {
|
|
712
|
-
;({ done, aborted, deadlineHit } = await runSequentialPass(targets, root,
|
|
745
|
+
;({ done, aborted, deadlineHit } = await runSequentialPass(targets, root, runOpts, stats, { reporter, emit }))
|
|
713
746
|
}
|
|
714
747
|
} finally {
|
|
715
748
|
reporter?.stop()
|
|
@@ -738,12 +771,13 @@ export async function runGenerationBatch(targets, root, opts = {}) {
|
|
|
738
771
|
*/
|
|
739
772
|
export function runDocFilesStampCli(argv) {
|
|
740
773
|
const root = resolveRoot(argv)
|
|
774
|
+
const testIndex = buildTestEvidenceIndex(root)
|
|
741
775
|
let stamped = 0
|
|
742
776
|
for (const file of scanForDocFiles(root)) {
|
|
743
777
|
const docAbs = join(root, file.docPath)
|
|
744
778
|
if (!existsSync(docAbs)) continue
|
|
745
779
|
const sourceAbs = join(root, file.sourcePath)
|
|
746
|
-
const crc =
|
|
780
|
+
const crc = documentationCrc(sourceAbs, testIndex)
|
|
747
781
|
const md = readFileSync(docAbs, 'utf8')
|
|
748
782
|
const { score, issues, judgeModel } = readDocQuality(docAbs)
|
|
749
783
|
const model = readDocModel(docAbs)
|
|
@@ -3,13 +3,33 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/doc-files/docgen-gen/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 55d2b8ff
|
|
7
7
|
model: openai-codex/gpt-5.4-mini
|
|
8
8
|
tier: cloud-min
|
|
9
|
-
score:
|
|
10
|
-
issues:
|
|
9
|
+
score: 50
|
|
10
|
+
issues: internal-name:isApiGap,internal-name:renderApiLine,internal-name:oneShotDoc,internal-name:finishUnsupported,anchor-miss:(foo.mdc),anchor-miss:(abie.mdc),best-of-2:retry-lost
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
## Огляд
|
|
14
|
+
|
|
15
|
+
Модуль формує лаконічну поведінкову документацію для коду через набір публічних кроків: `prepareBatchItem` готує окремий елемент, `generateDoc` створює текст документації, а `finishBatchItem` завершує обробку результату. Для локальних сценаріїв використовується `DEFAULT_LOCAL_MODEL`, а кешування працює у межах прогону, щоб повторні звернення в одному запуску не дублювали однакову роботу. Додаткові операції підтримують цілісність вхідного контексту (`capTimeoutToDeadline`, `stripLeadingPreamble`, `splitProtected`, `insertProtected`) і керують якістю та складом вихідного опису (`scoreDoc`, `buildApiSection`, `hasCompleteCommentDocumentation`, `commentDocumentationMode`, `insertTestScenarios`).
|
|
16
|
+
|
|
17
|
+
## Поведінка
|
|
18
|
+
|
|
19
|
+
Документ збирається як керований конвеєр: джерело й факти проходять preflight, після чого модуль або бере повністю детермінований шлях, або підключає локальну LLM-генерацію з подальшою перевіркою якості. Бюджет часу ріжеться через capTimeoutToDeadline, тож будь-який виклик не виходить за межі дедлайну; коли бюджет вичерпано, генерація зупиняється без старту нового запиту.
|
|
20
|
+
|
|
21
|
+
Початковий текст моделі очищується stripLeadingPreamble, щоб прибрати чатову самореференцію, а splitProtected та insertProtected зберігають захищену секцію «Призначення» під час усіх перетворень. Це важливо для повторних прогонів: наявний намір із попередньої документації не губиться, навіть якщо решта документа перегенеровується.mdc) мають зберігатися в точному вигляді, бо вони використовуються як дослівні якорі для перевірки відповідності.
|
|
22
|
+
|
|
23
|
+
Оцінка якості через scoreDoc працює на зібраному Markdown, а не на сирому відповіді моделі: секції спочатку нормалізуються, потім порівнюються з фактами, захищеним блоком і дослівними якорями. Саме цей score визначає, чи результат придатний одразу, чи потрібен повторний прохід, і чи слід позначати документ degraded. buildApiSection додає в документ лише те, що справді випливає з уже відомого public API, тому не дублює очевидне й не вигадує відсутні деталі.
|
|
24
|
+
|
|
25
|
+
hasCompleteCommentDocumentation і commentDocumentationMode керують тим, чи можна обійтися без LLM або обмежитися мінімальним доповненням з авторських коментарів. Якщо header і змістовні описи вже повністю покривають документ, генерація лишається детермінованою; якщо ж є лише часткові підказки, запускається comment-only або змішаний режим із коротким добудовуванням поведінки. У такому випадку commentDocumentationMode лише вибирає маршрут, а не переписує зміст.
|
|
26
|
+
|
|
27
|
+
insertTestScenarios додає окрему секцію сценаріїв з test/spec-файлів поверх уже зібраного Markdown, не змішуючи їх із поведінковим описом. Це дає змогу зберігати один і той самий основний текст документа незалежно від того, чи прийшов він із one-shot, batch або коментованого режиму.
|
|
28
|
+
|
|
29
|
+
DEFAULT_LOCAL_MODEL задає локальну модель за замовчуванням для всього конвеєра, а generateDoc є головною точкою входу: вона читає файл, отримує факти, вибирає режим, збирає промпт, викликає генерацію, рахує score і повертає готовий документ з метаданими. Якщо початкова версія не дотягує до порогу, виконується один повторний прохід із більш агресивним налаштуванням; якщо й він не допоміг, результат лишається degraded, але не ламає весь прогін.
|
|
30
|
+
|
|
31
|
+
prepareBatchItem і finishBatchItem ділять batch-потік на підготовку та фіналізацію: перша частина збирає все потрібне до submit, друга — перетворює вже отриманий текст у такий самий результат, як у послідовному шляху. Це забезпечує однакові правила для single-file і batch-режиму, але без дублювання LLM-викликів у batch-шарі та без змішування стану між файлами.
|
|
32
|
+
|
|
13
33
|
## Публічний API
|
|
14
34
|
|
|
15
35
|
- capTimeoutToDeadline — Ріже базовий per-call таймаут під залишок бюджету до дедлайну.
|
|
@@ -27,6 +47,14 @@ JSDoc-описом експорти рендеряться дослівно (`re
|
|
|
27
47
|
немає — секція збирається БЕЗ жодного LLM-виклику. Єдиний непокритий
|
|
28
48
|
експорт (як і раніше) лишається описаним лише в Поведінці — окремого виклику
|
|
29
49
|
на секцію з одного рядка не варте.
|
|
50
|
+
- hasCompleteCommentDocumentation — Чи коментарі автора повністю покривають машинну документацію: header дає
|
|
51
|
+
«Огляд», а змістовні описи всіх public API — відповідну секцію. У такому
|
|
52
|
+
разі LLM не потрібна: текст зберігається дослівно для JS, Rust і Python.
|
|
53
|
+
- commentDocumentationMode — Вибирає гібридний режим для повністю прокоментованого source. Короткий
|
|
54
|
+
header майже напевно є pointer-ом, а середній header разом із явним flow у
|
|
55
|
+
коді потребує короткого LLM-доповнення. Детальний наратив лишається 0-LLM.
|
|
56
|
+
- insertTestScenarios — Додає test-сценарії до one-shot/batch-документа. Для unsupported мов основний
|
|
57
|
+
Markdown ще повертає LLM, але test-секція лишається виключно JS-рендером.
|
|
30
58
|
- DEFAULT_LOCAL_MODEL — Дефолтна модель: N_CURSOR_DOCGEN_MODEL → resolveModel('min') (→ N_LOCAL_MIN_MODEL).
|
|
31
59
|
Без хардкод-fallback: модель налаштовує кожен локально (`N_LOCAL_MIN_MODEL`); якщо
|
|
32
60
|
нічого не задано — порожньо, і preflight оркестратора фейлить гучно (а не шле
|
|
@@ -48,6 +76,10 @@ pre-send guard і той самий факт-лист/one-shot messages, що й
|
|
|
48
76
|
викликається (мінімальний обсяг T8 — генерація; judge лишається опційним
|
|
49
77
|
розширенням послідовного шляху).
|
|
50
78
|
|
|
79
|
+
## Сценарії використання
|
|
80
|
+
|
|
81
|
+
- `npm/rules/doc-files/docgen-gen/tests/docgen-gen.test.mjs` (scoreDoc — R4 generic-overview; scoreDoc — R6 витік службових імен) — абстрактний Огляд штрафується і опускає score під поріг; конкретний Огляд не штрафується; неекспортована функція у Поведінці → internal-name; пропущений валідний анкор → anchor-miss + штраф; наявний анкор → без штрафу; ще 58
|
|
82
|
+
|
|
51
83
|
## Гарантії поведінки
|
|
52
84
|
|
|
53
85
|
- Кешує результати в межах одного прогону.
|