@7n/rules 1.49.25 → 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 +16 -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/docs/resolve-plugins.md +26 -53
- 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/scripts/lib/resolve-plugins.mjs +70 -6
- package/skills/doc-files/SKILL.md +21 -6
|
@@ -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
|
- Кешує результати в межах одного прогону.
|
|
@@ -8,6 +8,7 @@ import { startChain } from '@7n/llm-lib/chain'
|
|
|
8
8
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
9
9
|
import { docPathForSource } from '../docgen-scan/main.mjs'
|
|
10
10
|
import { loadDocFilesExtractors } from '../docgen-scan/lang-extensions.mjs'
|
|
11
|
+
import { buildTestEvidenceIndex, renderTestScenarios, testEvidenceForSource } from '../docgen-test-context/main.mjs'
|
|
11
12
|
import { extractAnchors, anchorTokens } from '../docgen-extract-anchors/main.mjs'
|
|
12
13
|
import { QUALITY_THRESHOLD } from '../docgen-crc/main.mjs'
|
|
13
14
|
import { JUDGE_ENABLED, JUDGE_MODEL, detectRefusalFiller, judgeDoc, judgeFailsDoc } from '../docgen-judge/main.mjs'
|
|
@@ -93,6 +94,13 @@ async function callLlm(messages, model, opts = {}) {
|
|
|
93
94
|
|
|
94
95
|
const FENCE_OPEN_RE = /^```[a-z]*\n?/
|
|
95
96
|
const FENCE_CLOSE_RE = /\n?```\s*$/
|
|
97
|
+
const EMPTY_INLINE_CODE_RE = /``/g
|
|
98
|
+
// Порожній code span — артефакт LLM, а не валідний факт про файл. Зрізаємо всю
|
|
99
|
+
// фразу: інакше після видалення лише `` лишається вигадане твердження довкола.
|
|
100
|
+
const EMPTY_INLINE_CODE_SENTENCE_RE = /[^.!?\n]*``[^.!?\n]*[.!?]/g
|
|
101
|
+
// Внутрішній приклад із коментарів на кшталт `(foo.mdc)` не є поведінкою
|
|
102
|
+
// модуля. Модель інколи переносить його у prose, тому таку фразу відкидаємо.
|
|
103
|
+
const PAREN_MDC_SENTENCE_RE = /[^.!?\n]*\([^()\n]+\.mdc\)[^.!?\n]*[.!?]/g
|
|
96
104
|
const LEADING_HEADING_RE = /^#{1,6}[ \t]{1,8}[^\n]{0,400}\n{1,8}/
|
|
97
105
|
// R9: чат-преамбули малих моделей — «озвучування завдання» перед відповіддю
|
|
98
106
|
// («Ось оновлена чорнетка секції…», «Як технічний письменник, я створю…»,
|
|
@@ -138,6 +146,11 @@ const PROTECTED_HEADING = 'Призначення'
|
|
|
138
146
|
const PROTECTED_START_RE = /^##\s+Призначення\s*$/
|
|
139
147
|
const H2_RE = /^##\s/
|
|
140
148
|
const H1_RE = /^#\s/
|
|
149
|
+
const SIGNATURE_CALL_RE = /([`\w$.]{1,80})\([^()]{0,300}\)/g
|
|
150
|
+
const NORMALIZED_SPACE_RE = /\s+/g
|
|
151
|
+
const H2_HEADING_RE = /^##\s.+$/gm
|
|
152
|
+
const TEST_SCENARIOS_HEADING = '## Сценарії використання'
|
|
153
|
+
const BEHAVIOR_HEADING = '## Поведінка'
|
|
141
154
|
|
|
142
155
|
/**
|
|
143
156
|
* R9: зрізає провідні чат-преамбули й дубль назви секції з початку тексту.
|
|
@@ -169,7 +182,11 @@ function stripSection(text) {
|
|
|
169
182
|
t = t.replace(FENCE_OPEN_RE, '').replace(FENCE_CLOSE_RE, '').trim()
|
|
170
183
|
}
|
|
171
184
|
t = t.replace(LEADING_HEADING_RE, '') // зрізати випадковий заголовок
|
|
172
|
-
return stripLeadingPreamble(t.trim())
|
|
185
|
+
return stripLeadingPreamble(t.trim())
|
|
186
|
+
.replaceAll(EMPTY_INLINE_CODE_SENTENCE_RE, '')
|
|
187
|
+
.replaceAll(PAREN_MDC_SENTENCE_RE, '')
|
|
188
|
+
.replaceAll(EMPTY_INLINE_CODE_RE, '')
|
|
189
|
+
.trim()
|
|
173
190
|
}
|
|
174
191
|
|
|
175
192
|
/**
|
|
@@ -181,7 +198,7 @@ function stripSection(text) {
|
|
|
181
198
|
*/
|
|
182
199
|
function stripSignatures(text) {
|
|
183
200
|
let t = text
|
|
184
|
-
for (let i = 0; i < 2; i++) t = t.replaceAll(
|
|
201
|
+
for (let i = 0; i < 2; i++) t = t.replaceAll(SIGNATURE_CALL_RE, '$1')
|
|
185
202
|
return t
|
|
186
203
|
}
|
|
187
204
|
|
|
@@ -424,6 +441,67 @@ export async function buildApiSection(facts, anchors, model, timeoutMs, temperat
|
|
|
424
441
|
return [coveredBlock, gapDraft].filter(Boolean).join('\n')
|
|
425
442
|
}
|
|
426
443
|
|
|
444
|
+
/**
|
|
445
|
+
* Чи коментарі автора повністю покривають машинну документацію: header дає
|
|
446
|
+
* «Огляд», а змістовні описи всіх public API — відповідну секцію. У такому
|
|
447
|
+
* разі LLM не потрібна: текст зберігається дослівно для JS, Rust і Python.
|
|
448
|
+
* @param {object} facts факт-лист мовного екстрактора
|
|
449
|
+
* @returns {boolean} true, якщо можна зібрати документ без LLM
|
|
450
|
+
*/
|
|
451
|
+
export function hasCompleteCommentDocumentation(facts) {
|
|
452
|
+
return Boolean(facts.header?.trim()) && (facts.exports ?? []).every(exp => !isApiGap(exp))
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Мінімальний розмір header, який може бути лише pointer-ом, а не наративом. */
|
|
456
|
+
const SHORT_HEADER_CHARS = 240
|
|
457
|
+
/** Один докладний public API може сам закрити короткий header-pointer. */
|
|
458
|
+
const SUFFICIENT_SINGLE_API_CHARS = 160
|
|
459
|
+
/** Сигнали потоку, який доцільно стисло звʼязати окремою «Поведінкою». */
|
|
460
|
+
const BEHAVIOR_FLOW_RE =
|
|
461
|
+
/\b(?:await|Promise\.all|Semaphore|JoinSet|chunks|concurr|retry|queue|state|workflow|resolver|provider|catch|try|match)\b/i
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Вибирає гібридний режим для повністю прокоментованого source. Короткий
|
|
465
|
+
* header майже напевно є pointer-ом, а середній header разом із явним flow у
|
|
466
|
+
* коді потребує короткого LLM-доповнення. Детальний наратив лишається 0-LLM.
|
|
467
|
+
* @param {object} facts факт-лист мовного екстрактора
|
|
468
|
+
* @param {string} src вміст source-файлу
|
|
469
|
+
* @returns {'fallback'|'comment-only'|'comment+behavior'} режим генерації
|
|
470
|
+
*/
|
|
471
|
+
export function commentDocumentationMode(facts, src) {
|
|
472
|
+
if (!hasCompleteCommentDocumentation(facts)) return 'fallback'
|
|
473
|
+
const headerSize = facts.header.trim().replaceAll(NORMALIZED_SPACE_RE, ' ').length
|
|
474
|
+
const exports = facts.exports ?? []
|
|
475
|
+
const apiSize = exports
|
|
476
|
+
.map(exp => exp.desc?.replaceAll(NORMALIZED_SPACE_RE, ' ').length ?? 0)
|
|
477
|
+
.reduce((sum, size) => sum + size, 0)
|
|
478
|
+
// У маленькому модулі один ретельно описаний API уже є поведінковим
|
|
479
|
+
// контрактом. LLM тут найчастіше додає загальні фрази замість глибини.
|
|
480
|
+
if (headerSize < SHORT_HEADER_CHARS && exports.length === 1 && apiSize >= SUFFICIENT_SINGLE_API_CHARS) {
|
|
481
|
+
return 'comment-only'
|
|
482
|
+
}
|
|
483
|
+
if (headerSize < SHORT_HEADER_CHARS) return 'comment+behavior'
|
|
484
|
+
if (headerSize < 900 && BEHAVIOR_FLOW_RE.test(src)) return 'comment+behavior'
|
|
485
|
+
return 'comment-only'
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Збирає документ лише з авторських коментарів і детермінованих фактів.
|
|
490
|
+
* @param {object} facts факт-лист мовного екстрактора
|
|
491
|
+
* @param {string|null} intent захищена секція «Призначення»
|
|
492
|
+
* @param {string} [behavior] додаткова LLM-секція «Поведінка» для comment+behavior режиму
|
|
493
|
+
*/
|
|
494
|
+
function commentOnlyDoc(facts, intent, behavior = '') {
|
|
495
|
+
const sections = {
|
|
496
|
+
overview: facts.header.trim(),
|
|
497
|
+
api: (facts.exports ?? []).map(exp => renderApiLine(exp)).join('\n'),
|
|
498
|
+
behavior,
|
|
499
|
+
scenarios: renderTestScenarios(facts.testScenarioFiles ?? []),
|
|
500
|
+
guarantees: guaranteesFromMarkers(facts)
|
|
501
|
+
}
|
|
502
|
+
return { md: insertProtected(assemble(basename(facts.relPath), sections), intent) }
|
|
503
|
+
}
|
|
504
|
+
|
|
427
505
|
/**
|
|
428
506
|
* One-shot: один виклик LLM на весь документ (для unsupported-структур).
|
|
429
507
|
* @param {object} facts факт-лист
|
|
@@ -437,7 +515,7 @@ async function oneShotDoc(facts, src, model, timeoutMs = LOCAL_TIMEOUT_MS, { int
|
|
|
437
515
|
const text = await callLlm(oneShotMessages(facts, src), model, { timeoutMs })
|
|
438
516
|
let md = stripSignatures(stripSection(text))
|
|
439
517
|
if (!md.startsWith('#')) md = `# ${basename(facts.relPath)}\n\n${md}`
|
|
440
|
-
return { md: insertProtected(md + '\n', intent) }
|
|
518
|
+
return { md: insertProtected(insertTestScenarios(md + '\n', facts), intent) }
|
|
441
519
|
}
|
|
442
520
|
|
|
443
521
|
/**
|
|
@@ -451,6 +529,7 @@ function assemble(stem, sections) {
|
|
|
451
529
|
['overview', '## Огляд'],
|
|
452
530
|
['behavior', '## Поведінка'],
|
|
453
531
|
['api', '## Публічний API'],
|
|
532
|
+
['scenarios', '## Сценарії використання'],
|
|
454
533
|
['guarantees', '## Гарантії поведінки']
|
|
455
534
|
]
|
|
456
535
|
const parts = [`# ${stem}`]
|
|
@@ -461,6 +540,55 @@ function assemble(stem, sections) {
|
|
|
461
540
|
return parts.join('\n\n') + '\n'
|
|
462
541
|
}
|
|
463
542
|
|
|
543
|
+
/**
|
|
544
|
+
* Видаляє H2-секцію з Markdown без regex з довільним тілом секції.
|
|
545
|
+
* @param {string} md зібраний Markdown-документ
|
|
546
|
+
* @param {string} heading точний H2-заголовок секції
|
|
547
|
+
* @returns {string} документ без секції
|
|
548
|
+
*/
|
|
549
|
+
function removeH2Section(md, heading) {
|
|
550
|
+
const lines = md.split('\n')
|
|
551
|
+
const start = lines.findIndex(line => line.trim() === heading)
|
|
552
|
+
if (start === -1) return md
|
|
553
|
+
const end = lines.findIndex((line, index) => index > start && H2_RE.test(line))
|
|
554
|
+
return [...lines.slice(0, start), ...lines.slice(end === -1 ? lines.length : end)].join('\n')
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Повертає тіло H2-секції з Markdown без regex з довільним тілом секції.
|
|
559
|
+
* @param {string} md зібраний Markdown-документ
|
|
560
|
+
* @param {string} heading точний H2-заголовок секції
|
|
561
|
+
* @returns {string} тіло секції або порожній рядок
|
|
562
|
+
*/
|
|
563
|
+
function h2SectionBody(md, heading) {
|
|
564
|
+
const lines = md.split('\n')
|
|
565
|
+
const start = lines.findIndex(line => line.trim() === heading)
|
|
566
|
+
if (start === -1) return ''
|
|
567
|
+
const end = lines.findIndex((line, index) => index > start && H2_RE.test(line))
|
|
568
|
+
return lines
|
|
569
|
+
.slice(start + 1, end === -1 ? lines.length : end)
|
|
570
|
+
.join('\n')
|
|
571
|
+
.trim()
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Додає test-сценарії до one-shot/batch-документа. Для unsupported мов основний
|
|
576
|
+
* Markdown ще повертає LLM, але test-секція лишається виключно JS-рендером.
|
|
577
|
+
* @param {string} md зібраний Markdown-документ
|
|
578
|
+
* @param {object} facts факт-лист із `testScenarioFiles`
|
|
579
|
+
* @returns {string} документ із детермінованою секцією сценаріїв
|
|
580
|
+
*/
|
|
581
|
+
export function insertTestScenarios(md, facts) {
|
|
582
|
+
const scenarios = renderTestScenarios(facts.testScenarioFiles ?? [])
|
|
583
|
+
if (!scenarios) return md
|
|
584
|
+
const withoutOld = removeH2Section(md, TEST_SCENARIOS_HEADING).trimEnd()
|
|
585
|
+
const section = `## Сценарії використання\n\n${scenarios}`
|
|
586
|
+
const guarantees = '\n\n## Гарантії поведінки'
|
|
587
|
+
const at = withoutOld.indexOf(guarantees)
|
|
588
|
+
if (at === -1) return `${withoutOld}\n\n${section}\n`
|
|
589
|
+
return `${withoutOld.slice(0, at)}\n\n${section}${withoutOld.slice(at)}\n`
|
|
590
|
+
}
|
|
591
|
+
|
|
464
592
|
/**
|
|
465
593
|
* Orchestrated: N окремих LLM-викликів, по одному на секцію.
|
|
466
594
|
* Код потрапляє лише в `behavior`; решта секцій — на мінімальному факт-листі.
|
|
@@ -478,10 +606,21 @@ async function orchestratedDoc(
|
|
|
478
606
|
timeoutMs,
|
|
479
607
|
{ anchors = null, temperature = 0.2, intent = null } = {}
|
|
480
608
|
) {
|
|
609
|
+
const commentMode = commentDocumentationMode(facts, src)
|
|
610
|
+
if (commentMode === 'comment-only') return commentOnlyDoc(facts, intent)
|
|
611
|
+
if (commentMode === 'comment+behavior') {
|
|
612
|
+
const [behaviorPrompt] = sectionMessages(facts, src, anchors, intent, { complementaryBehavior: true })
|
|
613
|
+
const behavior = stripSignatures(
|
|
614
|
+
stripSection(await callLlm(behaviorPrompt.messages, model, { timeoutMs, temperature }))
|
|
615
|
+
)
|
|
616
|
+
return { md: commentOnlyDoc(facts, intent, CRITIC_NONE_RE.test(behavior.trim()) ? '' : behavior).md }
|
|
617
|
+
}
|
|
481
618
|
const sections = {}
|
|
482
619
|
const anc = anchors ?? extractAnchors(src)
|
|
483
620
|
// E3: «Гарантії» — детермінований шаблон з markers (0 LLM-запитів, 0 generic-фраз)
|
|
484
621
|
sections.guarantees = guaranteesFromMarkers(facts)
|
|
622
|
+
// Test/spec назви рендеряться JS-ом дослівно, а не потрапляють у LLM prompt.
|
|
623
|
+
sections.scenarios = renderTestScenarios(facts.testScenarioFiles ?? [])
|
|
485
624
|
// Спершу Поведінка — єдина секція з кодом (sectionMessages повертає лише її)
|
|
486
625
|
for (const s of sectionMessages(facts, src, anc, intent)) {
|
|
487
626
|
sections[s.key] = stripSignatures(stripSection(await callLlm(s.messages, model, { timeoutMs, temperature })))
|
|
@@ -502,6 +641,27 @@ async function orchestratedDoc(
|
|
|
502
641
|
return { md: insertProtected(assemble(basename(facts.relPath), sections), intent) }
|
|
503
642
|
}
|
|
504
643
|
|
|
644
|
+
/**
|
|
645
|
+
* Semantic evidence для judge: лише source-код; test-сценарії формує JS.
|
|
646
|
+
* @param {string} src source-код
|
|
647
|
+
* @returns {string} evidence для judge
|
|
648
|
+
*/
|
|
649
|
+
function semanticEvidence(src) {
|
|
650
|
+
return src
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Витягує LLM-секцію «Поведінка» для вузького semantic judge. Авторські
|
|
655
|
+
* «Огляд»/API не оцінюються моделлю і не можуть бути нею переписані.
|
|
656
|
+
* @param {string} md зібраний Markdown-документ
|
|
657
|
+
* @returns {string} мінімальний документ з однією секцією або порожній рядок
|
|
658
|
+
*/
|
|
659
|
+
function behaviorOnlyDocument(md) {
|
|
660
|
+
const body = h2SectionBody(md, BEHAVIOR_HEADING)
|
|
661
|
+
return body ? `# Поведінка\n\n## Поведінка\n\n${body}\n` : ''
|
|
662
|
+
return match ? `# Поведінка\n\n## Поведінка\n\n${match[1].trim()}\n` : ''
|
|
663
|
+
}
|
|
664
|
+
|
|
505
665
|
/**
|
|
506
666
|
* №6 — judge-refine: суддя назвав конкретні неточності (`judge.reason`) — один
|
|
507
667
|
* локальний refine-прохід замість лише маркування degraded. Приймаємо виправлену
|
|
@@ -520,13 +680,13 @@ async function judgeRefinePass(r, judge, { facts, anchors, src, score, model, ch
|
|
|
520
680
|
if (!fixed.startsWith('#')) fixed = `# ${basename(facts.relPath)}\n\n${fixed}`
|
|
521
681
|
const fixedMd = insertProtected(fixed + '\n', intentBody)
|
|
522
682
|
// Guard 1: рерайт не має губити секції (малі моделі інколи повертають фрагмент)
|
|
523
|
-
const origHeadings = r.md.match(
|
|
683
|
+
const origHeadings = r.md.match(H2_HEADING_RE) ?? []
|
|
524
684
|
if (origHeadings.some(h => !fixedMd.includes(h))) return null
|
|
525
685
|
// Guard 2: det-score не має падати
|
|
526
686
|
const sFixed = scoreDoc(fixedMd, facts, { anchors, src })
|
|
527
687
|
if (sFixed.score < score) return null
|
|
528
688
|
// Guard 3: повторний суддя (той самий scope: inaccurate)
|
|
529
|
-
const judge2 = { ...(await judgeDoc(src, fixedMd, { chain })), model: JUDGE_MODEL }
|
|
689
|
+
const judge2 = { ...(await judgeDoc(semanticEvidence(src), fixedMd, { chain })), model: JUDGE_MODEL }
|
|
530
690
|
if (judgeFailsDoc(judge2)) return null
|
|
531
691
|
return { md: fixedMd, score: sFixed.score, issues: sFixed.issues, judge: judge2 }
|
|
532
692
|
}
|
|
@@ -541,7 +701,7 @@ async function judgeRefinePass(r, judge, { facts, anchors, src, score, model, ch
|
|
|
541
701
|
async function runJudgeGate({ r, score, issues, facts, anchors, src, model, chain }) {
|
|
542
702
|
let judge = null
|
|
543
703
|
try {
|
|
544
|
-
judge = { ...(await judgeDoc(src, r.md, { chain })), model: JUDGE_MODEL }
|
|
704
|
+
judge = { ...(await judgeDoc(semanticEvidence(src, facts), r.md, { chain })), model: JUDGE_MODEL }
|
|
545
705
|
// №6: суддя назвав конкретні неточності → один локальний refine-прохід
|
|
546
706
|
// (опт-аут: N_CURSOR_DOCGEN_JUDGE_REFINE=0). Прийнято лише коли всі
|
|
547
707
|
// guard-и judgeRefinePass пройдені; інакше — degraded, як раніше.
|
|
@@ -610,10 +770,13 @@ function srcTokenBudget() {
|
|
|
610
770
|
* (js/mjs/ts — lang-js, `.rs` — lang-rust; whole-file `unsupported`-fallback, якщо
|
|
611
771
|
* екстрактора для розширення нема).
|
|
612
772
|
* @param {string} file абсолютний шлях джерела
|
|
773
|
+
* @param {ReturnType<typeof buildTestEvidenceIndex>|null} [testIndex] source↔tests index
|
|
613
774
|
* @returns {Promise<{ src: string, estTokens: number, ext: string, langExtractors: Map<string, object>, facts: object }>} усе потрібне обом callers перед LLM-викликом
|
|
614
775
|
*/
|
|
615
|
-
async function loadSrcAndFacts(file) {
|
|
776
|
+
async function loadSrcAndFacts(file, testIndex = null) {
|
|
616
777
|
const src = readFileSync(file, 'utf8')
|
|
778
|
+
const evidenceIndex = testIndex ?? (existsSync(file) ? buildTestEvidenceIndex(process.cwd()) : null)
|
|
779
|
+
const testEvidence = evidenceIndex ? testEvidenceForSource(file, evidenceIndex) : { files: [] }
|
|
617
780
|
const estTokens = Math.round(Buffer.byteLength(src, 'utf8') / 4)
|
|
618
781
|
const budget = srcTokenBudget()
|
|
619
782
|
if (estTokens > budget) {
|
|
@@ -623,7 +786,7 @@ async function loadSrcAndFacts(file) {
|
|
|
623
786
|
}
|
|
624
787
|
const langExtractors = await loadDocFilesExtractors(process.cwd())
|
|
625
788
|
const ext = `.${file.split('.').pop()}`.toLowerCase()
|
|
626
|
-
const
|
|
789
|
+
const extractedFacts = langExtractors.get(ext)?.extractFacts?.(src, file) ?? {
|
|
627
790
|
relPath: file,
|
|
628
791
|
lang: ext.slice(1),
|
|
629
792
|
unsupported: true,
|
|
@@ -632,6 +795,10 @@ async function loadSrcAndFacts(file) {
|
|
|
632
795
|
imports: {},
|
|
633
796
|
markers: {}
|
|
634
797
|
}
|
|
798
|
+
const facts = {
|
|
799
|
+
...extractedFacts,
|
|
800
|
+
testScenarioFiles: testEvidence.files
|
|
801
|
+
}
|
|
635
802
|
return { src, estTokens, ext, langExtractors, facts }
|
|
636
803
|
}
|
|
637
804
|
|
|
@@ -674,7 +841,7 @@ function finishUnsupported(r, { t0, model, chainExtra }) {
|
|
|
674
841
|
* з вищою температурою (best-of-2); якщо й він не допоміг — результат
|
|
675
842
|
* позначається `degraded`, рішення про перегенерацію приймає batch/користувач.
|
|
676
843
|
* @param {string} file абсолютний шлях джерела
|
|
677
|
-
* @param {{ model?: string, threshold?: number, existingMd?: string|null, chainFactory?: typeof startChain, deadlineAt?: number|null }} [opts] model-id, поріг degraded, наявна дока (для збереження захищеної секції), фабрика ланцюжка (інжект для тестів), deadlineAt — мʼякий дедлайн fix-pipeline (epoch ms): per-call таймаути ріжуться під залишок бюджету, вичерпаний бюджет обриває генерацію transient
|
|
844
|
+
* @param {{ model?: string, threshold?: number, existingMd?: string|null, chainFactory?: typeof startChain, deadlineAt?: number|null, testIndex?: ReturnType<typeof buildTestEvidenceIndex>|null }} [opts] model-id, поріг degraded, наявна дока (для збереження захищеної секції), фабрика ланцюжка (інжект для тестів), deadlineAt — мʼякий дедлайн fix-pipeline (epoch ms): per-call таймаути ріжуться під залишок бюджету, вичерпаний бюджет обриває генерацію transient-помилкою; testIndex — спільний source↔tests index батчу
|
|
678
845
|
* @returns {{ md: string, ms: number, llmMs: number, llmCalls: number, score: number|null, issues: string[], degraded: boolean, model: string }} документ і метадані генерації (ms — увесь файл; llmMs/llmCalls — лише LLM; решта ms — оркестрація)
|
|
679
846
|
*/
|
|
680
847
|
export async function generateDoc(
|
|
@@ -684,11 +851,12 @@ export async function generateDoc(
|
|
|
684
851
|
threshold = QUALITY_THRESHOLD,
|
|
685
852
|
existingMd = null,
|
|
686
853
|
chainFactory = startChain,
|
|
687
|
-
deadlineAt = null
|
|
854
|
+
deadlineAt = null,
|
|
855
|
+
testIndex = null
|
|
688
856
|
} = {}
|
|
689
857
|
) {
|
|
690
858
|
// Guard ДО створення ланцюжка: skip без LLM — не задача.
|
|
691
|
-
const { src, estTokens, ext, langExtractors, facts } = await loadSrcAndFacts(file)
|
|
859
|
+
const { src, estTokens, ext, langExtractors, facts } = await loadSrcAndFacts(file, testIndex)
|
|
692
860
|
const t0 = Date.now()
|
|
693
861
|
llmMeter = { calls: 0, ms: 0 }
|
|
694
862
|
const chain = chainFactory({ kind: 'doc-generate', unit: facts.relPath, cwd: process.cwd() })
|
|
@@ -717,6 +885,7 @@ export async function generateDoc(
|
|
|
717
885
|
// Варіант B: захищена секція «Призначення» з наявної доки — зберегти й подати як контекст
|
|
718
886
|
const intent = existingMd ? splitProtected(existingMd).body : null
|
|
719
887
|
const anchors = facts.unsupported ? null : extractAnchors(src)
|
|
888
|
+
const commentMode = commentDocumentationMode(facts, src)
|
|
720
889
|
const promptSrc = resolvePromptSrc({ facts, estTokens, langExtractors, ext, src, file })
|
|
721
890
|
let r = facts.unsupported
|
|
722
891
|
? await oneShotDoc(facts, src, model, LOCAL_TIMEOUT_MS, { intent })
|
|
@@ -732,6 +901,49 @@ export async function generateDoc(
|
|
|
732
901
|
// Stage 2.5: детермінований скоринг (0 токенів)
|
|
733
902
|
let { score, issues } = scoreDoc(r.md, facts, { anchors, src })
|
|
734
903
|
|
|
904
|
+
// Авторські header/API-коментарі — уже джерело істини. Для comment-only
|
|
905
|
+
// LLM не запускається зовсім; comment+behavior судить лише LLM-секцію.
|
|
906
|
+
if (commentMode === 'comment-only') {
|
|
907
|
+
chainExtra.score = score
|
|
908
|
+
chainExtra.degraded = false
|
|
909
|
+
return {
|
|
910
|
+
...r,
|
|
911
|
+
ms: Date.now() - t0,
|
|
912
|
+
llmMs: llmMeter.ms,
|
|
913
|
+
llmCalls: llmMeter.calls,
|
|
914
|
+
score,
|
|
915
|
+
issues,
|
|
916
|
+
degraded: false,
|
|
917
|
+
model
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
if (commentMode === 'comment+behavior') {
|
|
922
|
+
const behaviorDoc = behaviorOnlyDocument(r.md)
|
|
923
|
+
let judge = null
|
|
924
|
+
if (JUDGE_ENABLED && behaviorDoc) {
|
|
925
|
+
judge = { ...(await judgeDoc(semanticEvidence(src), behaviorDoc, { chain })), model: JUDGE_MODEL }
|
|
926
|
+
if (judgeFailsDoc(judge)) {
|
|
927
|
+
r = commentOnlyDoc(facts, intent)
|
|
928
|
+
;({ score, issues } = scoreDoc(r.md, facts, { anchors, src }))
|
|
929
|
+
issues.push('behavior-judge-removed')
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
chainExtra.score = score
|
|
933
|
+
chainExtra.degraded = false
|
|
934
|
+
return {
|
|
935
|
+
...r,
|
|
936
|
+
ms: Date.now() - t0,
|
|
937
|
+
llmMs: llmMeter.ms,
|
|
938
|
+
llmCalls: llmMeter.calls,
|
|
939
|
+
score,
|
|
940
|
+
issues,
|
|
941
|
+
judge,
|
|
942
|
+
degraded: false,
|
|
943
|
+
model
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
735
947
|
// E4: best-of-2 — один retry з вищою температурою, det-вибір кращого
|
|
736
948
|
if (score < threshold && env.N_CURSOR_DOCGEN_BEST_OF !== '0') {
|
|
737
949
|
try {
|
|
@@ -786,14 +998,21 @@ export async function generateDoc(
|
|
|
786
998
|
* всі файли разом). Кидає ту саму помилку pre-send guard, що й `generateDoc`
|
|
787
999
|
* (класифікується `permanent` у batch-оркестраторі — skip, не помилка прогону).
|
|
788
1000
|
* @param {string} file абсолютний шлях джерела
|
|
789
|
-
* @param {{ existingMd?: string|null }} [opts] наявна дока (для захищеної секції «Призначення»)
|
|
790
|
-
* @returns {Promise<{ facts: object, anchors: object|null, src: string, messages: Array<{role:string,content:string}>, intent: string|null }>} усе потрібне для item-у batch-у й пізнішого
|
|
1001
|
+
* @param {{ existingMd?: string|null, testIndex?: ReturnType<typeof buildTestEvidenceIndex>|null }} [opts] наявна дока (для захищеної секції «Призначення») і спільний source↔tests index
|
|
1002
|
+
* @returns {Promise<{ facts: object, anchors: object|null, src: string, mode: string, messages: Array<{role:string,content:string}>, intent: string|null }>} усе потрібне для item-у batch-у й пізнішого фінішу; `comment-only` повертає порожні messages
|
|
791
1003
|
*/
|
|
792
|
-
export async function prepareBatchItem(file, { existingMd = null } = {}) {
|
|
793
|
-
const { src, facts } = await loadSrcAndFacts(file)
|
|
1004
|
+
export async function prepareBatchItem(file, { existingMd = null, testIndex = null } = {}) {
|
|
1005
|
+
const { src, facts } = await loadSrcAndFacts(file, testIndex)
|
|
794
1006
|
const anchors = facts.unsupported ? null : extractAnchors(src)
|
|
795
1007
|
const intent = existingMd ? splitProtected(existingMd).body : null
|
|
796
|
-
|
|
1008
|
+
const mode = facts.unsupported ? 'fallback' : commentDocumentationMode(facts, src)
|
|
1009
|
+
const messages =
|
|
1010
|
+
mode === 'comment-only'
|
|
1011
|
+
? []
|
|
1012
|
+
: mode === 'comment+behavior'
|
|
1013
|
+
? sectionMessages(facts, src, anchors, intent, { complementaryBehavior: true })[0].messages
|
|
1014
|
+
: oneShotMessages(facts, src)
|
|
1015
|
+
return { facts, anchors, src, mode, messages, intent }
|
|
797
1016
|
}
|
|
798
1017
|
|
|
799
1018
|
/**
|
|
@@ -803,13 +1022,28 @@ export async function prepareBatchItem(file, { existingMd = null } = {}) {
|
|
|
803
1022
|
* викликається (мінімальний обсяг T8 — генерація; judge лишається опційним
|
|
804
1023
|
* розширенням послідовного шляху).
|
|
805
1024
|
* @param {string} text сирий текст відповіді моделі для цього item-у
|
|
806
|
-
* @param {{ facts: object, anchors: object|null, src: string, intent: string|null, model: string, threshold?: number }} ctx контекст item-у (з `prepareBatchItem`)
|
|
1025
|
+
* @param {{ facts: object, anchors: object|null, src: string, intent: string|null, model: string, threshold?: number, mode?: string }} ctx контекст item-у (з `prepareBatchItem`)
|
|
807
1026
|
* @returns {{ md: string, score: number|null, issues: string[], degraded: boolean, model: string }} результат генерації для штампу/запису
|
|
808
1027
|
*/
|
|
809
|
-
export function finishBatchItem(
|
|
1028
|
+
export function finishBatchItem(
|
|
1029
|
+
text,
|
|
1030
|
+
{ facts, anchors, src, intent, model, threshold = QUALITY_THRESHOLD, mode = null }
|
|
1031
|
+
) {
|
|
1032
|
+
const commentMode = mode ?? (facts.unsupported ? 'fallback' : commentDocumentationMode(facts, src))
|
|
1033
|
+
if (commentMode === 'comment-only') {
|
|
1034
|
+
const md = commentOnlyDoc(facts, intent).md
|
|
1035
|
+
const { score, issues } = scoreDoc(md, facts, { anchors, src })
|
|
1036
|
+
return { md, score, issues, degraded: false, model }
|
|
1037
|
+
}
|
|
1038
|
+
if (commentMode === 'comment+behavior') {
|
|
1039
|
+
const behavior = stripSignatures(stripSection(text))
|
|
1040
|
+
const md = commentOnlyDoc(facts, intent, CRITIC_NONE_RE.test(behavior.trim()) ? '' : behavior).md
|
|
1041
|
+
const { score, issues } = scoreDoc(md, facts, { anchors, src })
|
|
1042
|
+
return { md, score, issues, degraded: false, model }
|
|
1043
|
+
}
|
|
810
1044
|
let md = stripSignatures(stripSection(text))
|
|
811
1045
|
if (!md.startsWith('#')) md = `# ${basename(facts.relPath)}\n\n${md}`
|
|
812
|
-
md = insertProtected(md + '\n', intent)
|
|
1046
|
+
md = insertProtected(insertTestScenarios(md + '\n', facts), intent)
|
|
813
1047
|
if (facts.unsupported) {
|
|
814
1048
|
const refusal = detectRefusalFiller(splitProtected(md).without)
|
|
815
1049
|
return {
|
|
@@ -3,33 +3,35 @@ type: JS Module
|
|
|
3
3
|
title: main.mjs
|
|
4
4
|
resource: npm/rules/doc-files/docgen-prompts/main.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
7
|
-
model: openai-codex/gpt-5.
|
|
8
|
-
tier: cloud-
|
|
6
|
+
crc: d09eacad
|
|
7
|
+
model: openai-codex/gpt-5.4-mini
|
|
8
|
+
tier: cloud-min
|
|
9
9
|
score: 100
|
|
10
|
-
issues: judge-refine:kept-original,judge:inaccurate:0.
|
|
10
|
+
issues: judge-refine:kept-original,judge:inaccurate:0.98
|
|
11
11
|
judgeModel: openai-codex/gpt-5.4-mini
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Огляд
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
`STYLE` задає спільний тон і межі викладу, а `sectionMessages` і `overviewMessages` узгоджують текст секції з уже відомими фактами. `isApiGap`, `apiGapMessages` і `renderApiLine` допомагають окремо фіксувати прогалини в API, щоб опис лишався чесним і не приписував зайвого. `criticMessages`, `refineMessages`, `oneShotMessages` і `judgeRefineMessages` підтримують послідовне доопрацювання одного й того ж змісту, а `guaranteesFromMarkers` і `UNIT_DIGEST_TOKENS` тримають описи в межах підтверджених маркерів і зведених одиниць.
|
|
17
17
|
|
|
18
18
|
## Поведінка
|
|
19
19
|
|
|
20
|
-
STYLE задає
|
|
20
|
+
STYLE задає спільний тон і межі для всього генератора: з нього випливають правила лаконічності, безпечної опори на факти та узгодженості між секціями.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
sectionMessages збирає мінімальний контекст для поведінкових секцій і запускає основний потік: factsSummary дає стислий факт-витяг, anchorsBlock додає лише потрібні анкори, intentContext підключає захищене «Призначення» як read-only фон, а msgs поєднує все у формат одного LLM-виклику. Саме тут зароджується текст секції «Поведінка», і саме тут враховується позначка про complementarity, коли LLM має доповнювати прогалини, а не переписувати вже відоме.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
isApiGap відсікає експорти, які вже мають змістовний JSDoc, від тих, що потребують синтезу; далі цей поділ живить окремий шлях для API, щоб не змішувати покритий текст із прогалинами. renderApiLine використовує лише вже описані експорти і переносить їх у публічний список без перефразування, а apiGapMessages працює тільки з невкритими експортами, щоб LLM не контактував із уже зафіксованим авторським формулюванням.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
overviewMessages формує завершальний узагальнювальний крок: бере вже написану «Поведінку», підкріплює її factsSummary та, за потреби, intentContext, і на цій основі просить Огляд. Це замикає порядок секцій так, щоб Огляд узгоджувався з готовим змістом, а не вигадувався з сирих фактів.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
criticMessages і refineMessages працюють парою як цикл якості для окремих секцій: критик звіряє чорнетку з facts і anchorsBlock та повертає конкретні issues, після чого refineMessages переписує текст тільки на підставі цих зауважень. Завдяки цьому правки залишаються локальними й не розмивають уже добрі частини секції.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
guaranteesFromMarkers не входить у LLM-потік: він детерміновано виводить гарантовані властивості з facts.markers і слугує стабільним шаром, який не залежить від генеративної варіативності. Це тримає критичні твердження поза ризиком галюцинацій.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
oneShotMessages є базовою точкою порівняння для одного суцільного LLM-запиту: до нього потрапляють facts і src без розбиття на секції. buildUnitDigest підміняє сирий src компактним структурним дайджестом, коли великий файл починає розмивати фокус моделі; UNIT_DIGEST_TOKENS обмежує цей режим, щоб зберігати промпт коротким і передбачуваним.
|
|
33
|
+
|
|
34
|
+
judgeRefineMessages підхоплює фінальне локальне доопрацювання після judge-перевірки: reason від судді стає єдиною опорою для точкового виправлення doc-тексту без залучення захищеного «Призначення». Увесь потік спирається на спільний кеш у межах одного прогону, щоб повторні звернення до тих самих даних не змінювали результат і не множили витрати.
|
|
33
35
|
|
|
34
36
|
## Публічний API
|
|
35
37
|
|
|
@@ -71,6 +73,10 @@ LLM-судді (замість лише маркування degraded). Судд
|
|
|
71
73
|
хибне (`reason`) — мала модель добре виправляє точкові твердження, коли їй
|
|
72
74
|
сказано, які саме.
|
|
73
75
|
|
|
76
|
+
## Сценарії використання
|
|
77
|
+
|
|
78
|
+
- `npm/rules/doc-files/docgen-prompts/tests/docgen-prompts.test.mjs` (sectionMessages — Огляд більше не тут (R3); guaranteesFromMarkers — лише file-local твердження) — не повертає секцію overview; Поведінка обмежена експортованими іменами (R6); Поведінка не отримує test evidence: сценарії рендерить JS окремою секцією; гібридний режим просить доповнити comments лише відсутнім потоком; fail-safe маркер не обіцяє, що всі помилки лишаються всередині модуля; ще 10
|
|
79
|
+
|
|
74
80
|
## Гарантії поведінки
|
|
75
81
|
|
|
76
82
|
- Власних операцій запису (ФС/БД) у файлі немає; виклики імпортованих модулів можуть писати.
|