@nitra/cursor 3.27.0 → 3.29.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 +17 -0
- package/package.json +1 -3
- package/rules/abie/js/applies.mjs +1 -5
- package/rules/abie/js/env_dns.mjs +1 -9
- package/rules/abie/js/firebase_hosting.mjs +1 -5
- package/rules/abie/js/hc_pairing.mjs +1 -8
- package/rules/abie/js/ua_http_route.mjs +1 -10
- package/rules/abie/js/ua_node_selector.mjs +1 -8
- package/rules/adr/js/hooks.mjs +1 -20
- package/rules/bun/js/layout.mjs +1 -19
- package/rules/capacitor/js/platforms.mjs +1 -23
- package/rules/changelog/js/consistency.mjs +1 -29
- package/rules/ci4/js/marksman_config.mjs +1 -19
- package/rules/docker/js/lint.mjs +1 -34
- package/rules/ga/docs/fix.md +4 -4
- package/rules/ga/js/docs/lint.md +3 -3
- package/rules/ga/js/docs/workflows.md +14 -14
- package/rules/ga/js/workflows.mjs +1 -16
- package/rules/ga/lint/docs/lint.md +9 -9
- package/rules/graphql/js/tooling.mjs +1 -9
- package/rules/hasura/js/internal_urls.mjs +1 -24
- package/rules/image-avif/js/avif_generation.mjs +1 -27
- package/rules/image-compress/js/package_setup.mjs +1 -18
- package/rules/js-bun-db/js/safety.mjs +1 -31
- package/rules/js-bun-redis/js/imports.mjs +1 -12
- package/rules/js-lint/js/docs/lint-findings.md +30 -0
- package/rules/js-lint/js/lint-findings.mjs +1 -7
- package/rules/js-lint/js/lint.mjs +1 -10
- package/rules/js-lint/js/tooling.mjs +1 -13
- package/rules/js-lint/js/utils_imports.mjs +1 -18
- package/rules/js-lint-ci/js/lint.mjs +1 -6
- package/rules/js-mssql/js/deps.mjs +1 -10
- package/rules/js-run/js/runtime.mjs +1 -37
- package/rules/js-run/lib/docs/temporal-scan.md +25 -0
- package/rules/k8s/js/manifests.mjs +1 -137
- package/rules/nginx-default-tpl/js/template.mjs +1 -18
- package/rules/npm-module/js/docs/header_doc_pointer.md +25 -0
- package/rules/npm-module/js/header_doc_pointer.mjs +82 -0
- package/rules/npm-module/js/package_structure.mjs +1 -28
- package/rules/npm-module/js/rule_meta.mjs +1 -10
- package/rules/npm-module/js/skill_meta.mjs +1 -13
- package/rules/php/js/tooling.mjs +1 -11
- package/rules/python/js/applies.mjs +1 -8
- package/rules/python/js/tooling.mjs +1 -21
- package/rules/rego/js/applies.mjs +1 -11
- package/rules/rust/js/applies.mjs +1 -7
- package/rules/security/js/sample_secret.mjs +1 -28
- package/rules/security/js/trufflehog.mjs +1 -8
- package/rules/style-lint/js/lint.mjs +1 -5
- package/rules/style-lint/js/tooling.mjs +1 -19
- package/rules/tauri/js/cargo_mutants_config.mjs +1 -20
- package/rules/tauri/js/tooling.mjs +1 -21
- package/rules/test/js/cargo_mutants_config.mjs +1 -12
- package/rules/test/js/location.mjs +1 -9
- package/rules/test/js/no-process-chdir.mjs +1 -21
- package/rules/test/js/no-relative-fs-path.mjs +1 -23
- package/rules/test/js/stryker_config.mjs +4 -25
- package/rules/test/js/vitest-config-pool-forks.mjs +1 -17
- package/rules/text/js/forbidden-prettier.mjs +1 -10
- package/rules/text/js/formatting.mjs +1 -31
- package/rules/vue/js/packages.mjs +1 -24
- package/scripts/coverage-classify/index.mjs +60 -72
- package/scripts/coverage-fix.mjs +26 -23
- package/scripts/dispatcher/lib/subagent-runner.mjs +33 -102
- package/scripts/docs/coverage-fix-extract.md +32 -0
- package/scripts/docs/lint-cli.md +25 -0
- package/scripts/docs/post-tool-use-fix.md +27 -0
- package/scripts/docs/rename-yaml-extensions.md +36 -0
- package/scripts/docs/skills-cli.md +35 -0
- package/scripts/docs/sync-claude-config.md +52 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +26 -0
- package/scripts/docs/upgrade-nitra-cursor-and-install.md +29 -0
- package/scripts/docs/worktree-cli.md +46 -0
- package/scripts/lib/docs/assert-project-root.md +28 -0
- package/scripts/lib/docs/diff-added-lines.md +34 -0
- package/scripts/lib/docs/read-n-cursor-config-lite.md +28 -0
- package/scripts/lib/docs/resolve-target-files.md +34 -0
- package/scripts/lib/docs/root-notice.md +28 -0
- package/scripts/lib/docs/rule-meta-helpers.md +34 -0
- package/scripts/lib/docs/rule-meta.md +34 -0
- package/scripts/lib/docs/rule-predicates.md +30 -0
- package/scripts/lib/docs/run-conftest-batch.md +26 -0
- package/scripts/lib/docs/run-lint-step.md +25 -0
- package/scripts/lib/docs/run-rule-cli.md +27 -0
- package/scripts/lib/docs/run-rule.md +32 -0
- package/scripts/lib/docs/run-standard-lint.md +22 -0
- package/scripts/lib/docs/run-standard-rule.md +24 -0
- package/scripts/lib/docs/skill-meta.md +31 -0
- package/scripts/lib/docs/sync-gitignore-worktree.md +31 -0
- package/scripts/lib/docs/template.md +40 -0
- package/scripts/lib/docs/timing-summary.md +24 -0
- package/scripts/lib/docs/workspaces.md +30 -0
- package/scripts/lib/docs/worktree-notice.md +27 -0
- package/scripts/lib/docs/worktree.md +38 -0
- package/scripts/utils/docs/ast-scan-utils.md +50 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +28 -0
- package/scripts/utils/docs/find-package-json-paths.md +26 -0
- package/scripts/utils/docs/lock-cache-dir.md +25 -0
- package/scripts/utils/docs/pass.md +25 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +23 -0
- package/scripts/utils/docs/resolve-cmd.md +29 -0
- package/scripts/utils/docs/resolve-js-root.md +25 -0
- package/scripts/utils/docs/test-helpers.md +36 -0
- package/scripts/utils/docs/walk-cache.md +27 -0
- package/scripts/utils/docs/walkDir.md +32 -0
- package/scripts/utils/docs/with-lock.md +25 -0
- package/scripts/utils/docs/worktree-fingerprint.md +27 -0
- package/skills/docgen/js/docgen-batch.mjs +95 -0
- package/skills/docgen/js/docgen-extract.mjs +33 -18
- package/skills/docgen/js/docgen-gen.mjs +140 -154
- package/skills/docgen/js/docgen-ignore.mjs +1 -6
- package/skills/docgen/js/docgen-prompts.mjs +33 -22
- package/skills/docgen/js/docgen-scan.mjs +1 -8
- package/skills/docgen/js/docs/docgen-extract.md +28 -0
- package/skills/docgen/js/docs/docgen-gen.md +41 -0
- package/skills/docgen/js/docs/docgen-ignore.md +24 -0
- package/skills/docgen/js/docs/docgen-prompts.md +24 -0
- package/skills/docgen/js/docs/docgen-scan.md +48 -0
- package/skills/fix/js/docs/llm-worker.md +27 -0
- package/skills/fix/js/docs/orchestrator.md +32 -0
- package/skills/fix/js/docs/t0.md +29 -0
- package/skills/fix/js/llm-worker.mjs +64 -29
- package/skills/fix/js/orchestrator.mjs +45 -54
- package/skills/fix/js/t0.mjs +16 -32
- package/skills/start-check/js/check.mjs +1 -16
- package/skills/start-check/js/docs/check.md +34 -0
- package/skills/taze/js/diff.mjs +1 -15
- package/skills/taze/js/docs/diff.md +33 -0
|
@@ -1,42 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* docgen-конвеєр (входна точка): код файлу → .md-документація.
|
|
3
|
-
*
|
|
4
|
-
* Інверсія керування: веде цей JS, а локальна модель — лише сервіс перефразування.
|
|
5
|
-
* Stage 0 extractFacts — факти з коду (0 токенів)
|
|
6
|
-
* Stage 1 sectionInstructions — точкові промпти на кожну секцію (спільний KV-cached префікс)
|
|
7
|
-
* Stage 2 stripSignatures — детермінований зріз сигнатур (0 токенів)
|
|
8
|
-
* Stage 2.5 scoreDoc — детермінований скоринг проти фактів (0 токенів)
|
|
9
|
-
* Stage 3 assemble — фіксовані заголовки/порядок + зрізання fence
|
|
10
|
-
* Tier 2 claudeOneShot — хмарний fallback якщо score < QUALITY_THRESHOLD
|
|
11
|
-
*
|
|
12
|
-
* Hybrid routing (sym-threshold):
|
|
13
|
-
* sym < BORDERLINE_SYM_LOW → Tier 1 local (без хмарного рефері)
|
|
14
|
-
* sym ∈ [BORDERLINE_SYM_LOW, sym<4) → Tier 1 + cloudScoreDoc (Haiku) → при низькому балі → Tier 2
|
|
15
|
-
* sym >= DEFAULT_SYM_THRESHOLD → одразу Tier 2 (pre-routing, без local)
|
|
16
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-gen.md */
|
|
17
2
|
import { readFileSync } from 'node:fs'
|
|
18
3
|
import { basename } from 'node:path'
|
|
19
4
|
import { request } from 'node:http'
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
20
6
|
import { env } from 'node:process'
|
|
21
|
-
import
|
|
7
|
+
import { LOCAL_MIN, resolveModel } from '../../../lib/models.mjs'
|
|
22
8
|
import { extractFacts } from './docgen-extract.mjs'
|
|
23
9
|
import { sectionMessages, oneShotMessages, STYLE, oneShotPromptText } from './docgen-prompts.mjs'
|
|
24
10
|
|
|
11
|
+
/** Strips provider prefix from tier string for direct ollama HTTP (ollama/gemma3:4b → gemma3:4b). */
|
|
12
|
+
function localModelId(tier) {
|
|
13
|
+
if (!tier) return 'gemma3:4b'
|
|
14
|
+
const i = tier.indexOf('/')
|
|
15
|
+
return i === -1 ? tier : tier.slice(i + 1)
|
|
16
|
+
}
|
|
17
|
+
|
|
25
18
|
const QUALITY_THRESHOLD = 70
|
|
26
19
|
|
|
27
20
|
/** Один виклик чату до ollama зі streaming (токени стримуються → socket активний, жодного timeout). */
|
|
28
21
|
async function ollamaChat(messages, { model, numPredict = 600 }) {
|
|
29
22
|
const body = JSON.stringify({
|
|
30
|
-
model,
|
|
23
|
+
model,
|
|
24
|
+
messages,
|
|
25
|
+
stream: true,
|
|
26
|
+
think: false,
|
|
31
27
|
options: { num_ctx: 8192, temperature: 0.2, num_predict: numPredict },
|
|
32
28
|
keep_alive: '15m'
|
|
33
29
|
})
|
|
34
30
|
return new Promise((resolve, reject) => {
|
|
35
31
|
const req = request(
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
{
|
|
33
|
+
hostname: 'localhost',
|
|
34
|
+
port: 11434,
|
|
35
|
+
path: '/api/chat',
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
|
38
|
+
},
|
|
39
|
+
res => {
|
|
40
|
+
let text = '',
|
|
41
|
+
genTok = 0,
|
|
42
|
+
buf = ''
|
|
40
43
|
res.on('data', chunk => {
|
|
41
44
|
buf += chunk.toString()
|
|
42
45
|
const lines = buf.split('\n')
|
|
@@ -47,7 +50,9 @@ async function ollamaChat(messages, { model, numPredict = 600 }) {
|
|
|
47
50
|
const j = JSON.parse(line)
|
|
48
51
|
text += j.message?.content ?? ''
|
|
49
52
|
if (j.done) genTok = j.eval_count ?? 0
|
|
50
|
-
} catch {
|
|
53
|
+
} catch {
|
|
54
|
+
/* partial line */
|
|
55
|
+
}
|
|
51
56
|
}
|
|
52
57
|
})
|
|
53
58
|
res.on('end', () => resolve({ text, genTok }))
|
|
@@ -64,7 +69,10 @@ async function ollamaChat(messages, { model, numPredict = 600 }) {
|
|
|
64
69
|
function stripSection(text) {
|
|
65
70
|
let t = text.trim()
|
|
66
71
|
if (t.startsWith('```')) {
|
|
67
|
-
t = t
|
|
72
|
+
t = t
|
|
73
|
+
.replace(/^```[a-z]*\n?/, '')
|
|
74
|
+
.replace(/\n?```\s*$/, '')
|
|
75
|
+
.trim()
|
|
68
76
|
}
|
|
69
77
|
t = t.replace(/^#{1,6}\s+.*\n+/, '') // зрізати випадковий заголовок
|
|
70
78
|
return t.trim()
|
|
@@ -87,8 +95,10 @@ function parseSections(md) {
|
|
|
87
95
|
let cur = null
|
|
88
96
|
for (const line of md.split('\n')) {
|
|
89
97
|
const m = line.match(/^##\s+(.+)/)
|
|
90
|
-
if (m) {
|
|
91
|
-
|
|
98
|
+
if (m) {
|
|
99
|
+
cur = m[1].toLowerCase().replace(/[^а-яіїєґa-z0-9]/gi, '')
|
|
100
|
+
result[cur] = ''
|
|
101
|
+
} else if (cur) result[cur] += line + '\n'
|
|
92
102
|
}
|
|
93
103
|
return result
|
|
94
104
|
}
|
|
@@ -102,93 +112,53 @@ function scoreDoc(md, facts) {
|
|
|
102
112
|
let score = 100
|
|
103
113
|
const issues = []
|
|
104
114
|
|
|
105
|
-
if (!s['огляд'])
|
|
106
|
-
|
|
115
|
+
if (!s['огляд']) {
|
|
116
|
+
score -= 25
|
|
117
|
+
issues.push('no-overview')
|
|
118
|
+
}
|
|
107
119
|
|
|
108
120
|
const behavior = s['поведінка'] ?? ''
|
|
109
|
-
if (behavior.length < 60)
|
|
110
|
-
|
|
121
|
+
if (behavior.length < 60) {
|
|
122
|
+
score -= 20
|
|
123
|
+
issues.push('short-behavior')
|
|
124
|
+
}
|
|
111
125
|
|
|
112
126
|
const guarantees = s['гарантіїповедінки'] ?? ''
|
|
113
127
|
// Будь-яка згадка "кеш" у Гарантіях коли файл не кешує — галюцинація
|
|
114
128
|
// Негація: "не кешує", "не має кешування", "без кешування", "немає кешу"
|
|
115
129
|
const cacheHit = /кеш/i.test(guarantees) && !/(?:не|без)\s+(?:\S+\s+)?кеш|немає\s+кеш/i.test(guarantees)
|
|
116
|
-
if (!facts.markers?.caches && cacheHit)
|
|
117
|
-
|
|
130
|
+
if (!facts.markers?.caches && cacheHit) {
|
|
131
|
+
score -= 20
|
|
132
|
+
issues.push('cache-hallucination')
|
|
133
|
+
}
|
|
118
134
|
|
|
119
135
|
// Перевіряємо лише бектік-обгорнуті імена (`sym`) — уникаємо substring false positives
|
|
120
136
|
const hasName = (text, sym) => text.includes('`' + sym + '`')
|
|
121
137
|
for (const sym of facts.internalSymbols ?? []) {
|
|
122
138
|
const inDoc = hasName(guarantees, sym) || hasName(s['огляд'] ?? '', sym) || hasName(s['поведінка'] ?? '', sym)
|
|
123
|
-
if (inDoc) {
|
|
139
|
+
if (inDoc) {
|
|
140
|
+
score -= 10
|
|
141
|
+
issues.push(`internal-name:${sym}`)
|
|
142
|
+
}
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
return { score: Math.max(0, score), issues }
|
|
127
146
|
}
|
|
128
147
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Відповідай ТІЛЬКИ JSON без пояснень:
|
|
137
|
-
{"огляд":N,"поведінка":N,"гарантії":N,"стиль":N,"issues":["коротко про кожен мінус 1-5 слів"]}`
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Stage 2.5 cloud: Claude Haiku оцінює якість доку проти коду + фактів.
|
|
141
|
-
* Використовує найдешевшу хмарну модель — haiku — для мінімальної вартості судді.
|
|
142
|
-
* @returns {{ score: number, scores: object, issues: string[], tok: number }}
|
|
143
|
-
*/
|
|
144
|
-
async function cloudScoreDoc(md, facts, src, model = 'claude-haiku-4-5-20251001') {
|
|
145
|
-
const client = new Anthropic()
|
|
146
|
-
const factsTxt = [
|
|
147
|
-
facts.exports?.length ? `Публічні функції: ${facts.exports.map(e => e.name).join(', ')}` : '',
|
|
148
|
-
facts.internalSymbols?.length ? `Внутрішні (не публічні): ${facts.internalSymbols.join(', ')}` : '',
|
|
149
|
-
facts.markers?.caches ? 'Кешування: є' : 'Кешування: немає',
|
|
150
|
-
facts.markers?.network ? 'Мережа: є' : 'Мережа: немає',
|
|
151
|
-
facts.markers?.readOnly ? 'Read-only (не змінює файли/стан)' : ''
|
|
152
|
-
].filter(Boolean).join('\n')
|
|
153
|
-
|
|
154
|
-
const msg = await client.messages.create({
|
|
155
|
-
model,
|
|
156
|
-
max_tokens: 256,
|
|
157
|
-
system: SCORE_RUBRIC,
|
|
158
|
-
messages: [{
|
|
159
|
-
role: 'user',
|
|
160
|
-
content: [
|
|
161
|
-
{ type: 'text', text: `ФАКТИ:\n${factsTxt}`, cache_control: { type: 'ephemeral' } },
|
|
162
|
-
{ type: 'text', text: `КОД:\n\`\`\`\n${src.slice(0, 4000)}\n\`\`\``, cache_control: { type: 'ephemeral' } },
|
|
163
|
-
{ type: 'text', text: `ДОКУМЕНТАЦІЯ:\n${md}` }
|
|
164
|
-
]
|
|
165
|
-
}]
|
|
166
|
-
})
|
|
167
|
-
const tok = (msg.usage?.input_tokens ?? 0) + (msg.usage?.output_tokens ?? 0)
|
|
168
|
-
try {
|
|
169
|
-
const j = JSON.parse(msg.content[0]?.text ?? '{}')
|
|
170
|
-
const total = ((j.огляд ?? 0) + (j.поведінка ?? 0) + (j.гарантії ?? 0) + (j.стиль ?? 0)) / 12 * 100
|
|
171
|
-
return { score: Math.round(total), scores: j, issues: j.issues ?? [], tok }
|
|
172
|
-
} catch {
|
|
173
|
-
return { score: 50, scores: {}, issues: ['parse-error'], tok }
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Tier 2: хмарний fallback через Claude коли local-score < QUALITY_THRESHOLD. */
|
|
178
|
-
async function claudeOneShot(facts, src, model = 'claude-sonnet-4-6') {
|
|
179
|
-
const client = new Anthropic()
|
|
180
|
-
const prompt = oneShotPromptText(facts, src)
|
|
181
|
-
const msg = await client.messages.create({
|
|
182
|
-
model,
|
|
183
|
-
max_tokens: 1500,
|
|
184
|
-
system: STYLE,
|
|
185
|
-
messages: [{ role: 'user', content: prompt }]
|
|
148
|
+
/** Tier 2: виклик через pi (провайдер-нейтрально). model — рядок `provider/model-id`. */
|
|
149
|
+
function piOneShot(facts, src, model) {
|
|
150
|
+
const fullPrompt = `${STYLE}\n\n${oneShotPromptText(facts, src)}`
|
|
151
|
+
const modelArgs = model ? ['--model', model] : []
|
|
152
|
+
const r = spawnSync('pi', ['-p', fullPrompt, ...modelArgs, '--no-session', '--mode', 'text', '--no-tools'], {
|
|
153
|
+
encoding: 'utf8',
|
|
154
|
+
timeout: 120_000
|
|
186
155
|
})
|
|
187
|
-
|
|
188
|
-
|
|
156
|
+
if (r.error) throw new Error(`pi Tier 2 error: ${r.error.message}`)
|
|
157
|
+
if (r.status !== 0) throw new Error(`pi Tier 2 exit ${r.status}: ${r.stderr?.slice(0, 300) ?? ''}`)
|
|
158
|
+
const text = r.stdout?.trim() ?? ''
|
|
189
159
|
let md = stripSignatures(stripSection(text))
|
|
190
160
|
if (!md.startsWith('#')) md = `# ${basename(facts.relPath)}\n\n${md}`
|
|
191
|
-
return { md: md + '\n', genTok }
|
|
161
|
+
return { md: md + '\n', genTok: 0 }
|
|
192
162
|
}
|
|
193
163
|
|
|
194
164
|
/** Stage 3: фіксовані заголовки у фіксованому порядку. */
|
|
@@ -229,106 +199,122 @@ async function generateOneShot(facts, src, model) {
|
|
|
229
199
|
|
|
230
200
|
/** Файли з sym ≥ цього значення одразу йдуть у Tier 2 (без локального проходу). */
|
|
231
201
|
const DEFAULT_SYM_THRESHOLD = 4
|
|
232
|
-
/**
|
|
233
|
-
const
|
|
202
|
+
/** Максимальний час локальної генерації на один файл перед ескалацією у Tier 2. */
|
|
203
|
+
const LOCAL_TIMEOUT_MS = 5 * 60 * 1000
|
|
204
|
+
/** Дефолтна Tier 1 модель: N_CURSOR_DOCGEN_MODEL → LOCAL_MIN → ollama gemma3:4b. */
|
|
205
|
+
const DEFAULT_LOCAL_MODEL = localModelId(env.N_CURSOR_DOCGEN_MODEL ?? LOCAL_MIN)
|
|
206
|
+
/** Дефолтна Tier 2 модель (provider/model-id для pi): N_CURSOR_DOCGEN_CLOUD_MODEL → resolveModel('avg'). */
|
|
207
|
+
const DEFAULT_CLOUD_MODEL = env.N_CURSOR_DOCGEN_CLOUD_MODEL ?? resolveModel('avg')
|
|
208
|
+
|
|
209
|
+
/** Повертає promise, що відхиляється через `ms` мс з повідомленням про timeout. */
|
|
210
|
+
function withTimeout(promise, ms) {
|
|
211
|
+
return Promise.race([
|
|
212
|
+
promise,
|
|
213
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`local timeout after ${ms / 1000}s`)), ms))
|
|
214
|
+
])
|
|
215
|
+
}
|
|
234
216
|
|
|
235
217
|
/**
|
|
236
218
|
* Головний API: файл → { md, genTok, ms, score, issues, tier }.
|
|
237
219
|
*
|
|
238
220
|
* Routing (sym-threshold):
|
|
239
|
-
* sym <
|
|
240
|
-
*
|
|
241
|
-
* sym >= symThreshold
|
|
242
|
-
* scoreCloud=true → примусово запускає cloudScoreDoc для всіх Tier 1
|
|
221
|
+
* sym < symThreshold → Tier 1 local (timeout: LOCAL_TIMEOUT_MS) + det-scorer
|
|
222
|
+
* → timeout або det-score < threshold → Tier 2
|
|
223
|
+
* sym >= symThreshold → Pre-routing одразу Tier 2
|
|
243
224
|
*
|
|
244
|
-
* @param {string} scoreModel — модель для хмарного рефері (Haiku за замовч.)
|
|
245
225
|
* @param {string} cloudModel — модель для Tier 2 генерації (Sonnet за замовч.)
|
|
246
|
-
* @param {boolean} scoreCloud — якщо true, cloudScoreDoc запускається для всіх Tier 1 файлів
|
|
247
226
|
*/
|
|
248
|
-
export async function generateDoc(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
} = {}
|
|
227
|
+
export async function generateDoc(
|
|
228
|
+
file,
|
|
229
|
+
{
|
|
230
|
+
model = DEFAULT_LOCAL_MODEL,
|
|
231
|
+
mode = 'orchestrated',
|
|
232
|
+
cloudModel = DEFAULT_CLOUD_MODEL,
|
|
233
|
+
threshold = QUALITY_THRESHOLD,
|
|
234
|
+
symThreshold = DEFAULT_SYM_THRESHOLD
|
|
235
|
+
} = {}
|
|
236
|
+
) {
|
|
257
237
|
const src = readFileSync(file, 'utf8')
|
|
258
238
|
const facts = extractFacts(src, file)
|
|
259
239
|
const t0 = Date.now()
|
|
260
240
|
|
|
261
241
|
// Pre-routing: складні файли (sym ≥ symThreshold) → одразу Tier 2, не витрачаємо local-час
|
|
262
242
|
const complexity = facts.internalSymbols?.length ?? 0
|
|
263
|
-
if (complexity >= symThreshold &&
|
|
264
|
-
const r2 =
|
|
265
|
-
return {
|
|
243
|
+
if (complexity >= symThreshold && cloudModel) {
|
|
244
|
+
const r2 = piOneShot(facts, src, cloudModel)
|
|
245
|
+
return {
|
|
246
|
+
...r2,
|
|
247
|
+
ms: Date.now() - t0,
|
|
248
|
+
score: null,
|
|
249
|
+
issues: [`pre-routed:sym=${complexity}`],
|
|
250
|
+
tier: 2,
|
|
251
|
+
model: cloudModel
|
|
252
|
+
}
|
|
266
253
|
}
|
|
267
254
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
255
|
+
// Tier 1: локальна генерація з timeout 5 хв — при перевищенні одразу Tier 2
|
|
256
|
+
let r
|
|
257
|
+
try {
|
|
258
|
+
const localPromise =
|
|
259
|
+
facts.unsupported || mode === 'oneshot'
|
|
260
|
+
? generateOneShot(facts, src, model)
|
|
261
|
+
: generateOrchestrated(facts, src, model)
|
|
262
|
+
r = await withTimeout(localPromise, LOCAL_TIMEOUT_MS)
|
|
263
|
+
} catch (e) {
|
|
264
|
+
if (cloudModel) {
|
|
265
|
+
const r2 = piOneShot(facts, src, cloudModel)
|
|
266
|
+
return {
|
|
267
|
+
...r2,
|
|
268
|
+
ms: Date.now() - t0,
|
|
269
|
+
score: null,
|
|
270
|
+
issues: [`local-timeout: ${e.message}`],
|
|
271
|
+
tier: 2,
|
|
272
|
+
model: cloudModel
|
|
273
|
+
}
|
|
285
274
|
}
|
|
286
|
-
|
|
287
|
-
issues: cs.issues, detScore, detIssues, tier: 1 }
|
|
275
|
+
throw e
|
|
288
276
|
}
|
|
289
277
|
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
278
|
+
// Stage 2.5: детермінований скоринг (0 токенів) — gate перед Tier 2
|
|
279
|
+
const { score: detScore, issues: detIssues } = scoreDoc(r.md, facts)
|
|
280
|
+
|
|
281
|
+
if (detScore < threshold && cloudModel) {
|
|
282
|
+
const r2 = piOneShot(facts, src, cloudModel)
|
|
283
|
+
return { ...r2, ms: Date.now() - t0, score: detScore, issues: detIssues, tier: 2, model: cloudModel }
|
|
294
284
|
}
|
|
295
285
|
|
|
296
|
-
return { ...r, ms: Date.now() - t0, score: detScore, issues: detIssues, tier: 1 }
|
|
286
|
+
return { ...r, ms: Date.now() - t0, score: detScore, issues: detIssues, tier: 1, model }
|
|
297
287
|
}
|
|
298
288
|
|
|
299
|
-
// CLI: node docgen-gen.mjs <file> [--oneshot] [--
|
|
289
|
+
// CLI: node docgen-gen.mjs <file> [--oneshot] [--model <m>] [--sym-threshold N] [--tier-only]
|
|
300
290
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
301
291
|
if (isRunAsCli(import.meta.url)) {
|
|
302
292
|
const args = process.argv.slice(2)
|
|
303
293
|
const file = args.find(a => !a.startsWith('--'))
|
|
304
294
|
const mode = args.includes('--oneshot') ? 'oneshot' : 'orchestrated'
|
|
305
|
-
const scoreCloud = args.includes('--score-cloud')
|
|
306
295
|
const tierOnly = args.includes('--tier-only')
|
|
307
|
-
const mi = args.indexOf('--model')
|
|
308
|
-
const
|
|
309
|
-
const si = args.indexOf('--sym-threshold')
|
|
296
|
+
const mi = args.indexOf('--model')
|
|
297
|
+
const model = mi >= 0 ? args[mi + 1] : DEFAULT_LOCAL_MODEL
|
|
298
|
+
const si = args.indexOf('--sym-threshold')
|
|
299
|
+
const symThreshold = si >= 0 ? Number(args[si + 1]) : DEFAULT_SYM_THRESHOLD
|
|
310
300
|
if (!file) {
|
|
311
|
-
console.error('Usage: node docgen-gen.mjs <file> [--oneshot] [--
|
|
301
|
+
console.error('Usage: node docgen-gen.mjs <file> [--oneshot] [--model <m>] [--sym-threshold N] [--tier-only]')
|
|
312
302
|
process.exit(1)
|
|
313
303
|
}
|
|
314
304
|
if (tierOnly) {
|
|
315
305
|
const src = readFileSync(file, 'utf8')
|
|
316
306
|
const facts = extractFacts(src, file)
|
|
317
307
|
const sym = facts.internalSymbols?.length ?? 0
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
} else {
|
|
324
|
-
icon = '💻'; label = `Tier 1 local (sym=${sym} < ${BORDERLINE_SYM_LOW})`
|
|
325
|
-
}
|
|
308
|
+
const icon = sym >= symThreshold ? '☁️ ' : '💻'
|
|
309
|
+
const label =
|
|
310
|
+
sym >= symThreshold
|
|
311
|
+
? `Tier 2 cloud (sym=${sym} ≥ ${symThreshold}, pre-routed)`
|
|
312
|
+
: `Tier 1 local (sym=${sym} < ${symThreshold})`
|
|
326
313
|
process.stdout.write(`${icon} ${label} | ${file}\n`)
|
|
327
314
|
process.exit(0)
|
|
328
315
|
}
|
|
329
|
-
const r = await generateDoc(file, { model, mode,
|
|
316
|
+
const r = await generateDoc(file, { model, mode, symThreshold })
|
|
330
317
|
const issuesTxt = r.issues?.length ? ` issues=${r.issues.join(',')}` : ''
|
|
331
|
-
|
|
332
|
-
process.stderr.write(`[tier${r.tier} ${mode}] ${r.ms}ms / ${r.genTok} tok / score=${r.score}${issuesTxt}${cloudTxt}\n`)
|
|
318
|
+
process.stderr.write(`[tier${r.tier} ${mode}] ${r.ms}ms / ${r.genTok} tok / score=${r.score}${issuesTxt}\n`)
|
|
333
319
|
process.stdout.write(r.md)
|
|
334
320
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Глоби, які `docgen` завжди ігнорує.
|
|
3
|
-
*
|
|
4
|
-
* Це окремий snippet-модуль: список правиться тут, scanner лише читає його
|
|
5
|
-
* через predicate. Патерни пишуться в posix-формі відносно кореня проєкту.
|
|
6
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-ignore.md */
|
|
7
2
|
import picomatch from 'picomatch'
|
|
8
3
|
|
|
9
4
|
/** Базовий список glob-ів для `docgen` ignore. */
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stage 1 docgen-конвеєра: факт-лист + код → точкові промпти на кожну секцію.
|
|
3
|
-
*
|
|
4
|
-
* v2 — СЕКЦІЙНО-МІНІМАЛЬНИЙ контекст: код іде ЛИШЕ у `Поведінку`. `Огляд` бере тільки
|
|
5
|
-
* header, `API` — лише список експортів, `Гарантії` — лише markers. Так інгест коду
|
|
6
|
-
* оплачується один раз (а не на кожну секцію), і оркестрація перестає програвати в часі.
|
|
7
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-prompts.md */
|
|
8
2
|
|
|
9
3
|
export const STYLE = [
|
|
10
4
|
'Ти технічний письменник. Пишеш лаконічну ПОВЕДІНКОВУ документацію до коду українською, чистим Markdown.',
|
|
@@ -28,7 +22,10 @@ function factsSummary(facts) {
|
|
|
28
22
|
return lines.join('\n')
|
|
29
23
|
}
|
|
30
24
|
|
|
31
|
-
const msgs = (system, user) => [
|
|
25
|
+
const msgs = (system, user) => [
|
|
26
|
+
{ role: 'system', content: system },
|
|
27
|
+
{ role: 'user', content: user }
|
|
28
|
+
]
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
31
|
* Секційні набори messages з МІНІМАЛЬНИМ контекстом під кожну секцію.
|
|
@@ -42,33 +39,45 @@ export function sectionMessages(facts, src) {
|
|
|
42
39
|
|
|
43
40
|
// Огляд — лише факти (без коду)
|
|
44
41
|
out.push({
|
|
45
|
-
key: 'overview',
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
key: 'overview',
|
|
43
|
+
numPredict: 220,
|
|
44
|
+
messages: msgs(
|
|
45
|
+
`${STYLE}\n\nВІДОМІ ФАКТИ:\n${factsTxt}`,
|
|
46
|
+
'Напиши вміст секції «Огляд»: 1-3 речення — що файл робить і навіщо існує (роль у системі). Без заголовка, без переліку функцій.'
|
|
47
|
+
)
|
|
48
48
|
})
|
|
49
49
|
|
|
50
50
|
// Поведінка — ЄДИНА секція, якій потрібен код
|
|
51
51
|
out.push({
|
|
52
|
-
key: 'behavior',
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
key: 'behavior',
|
|
53
|
+
numPredict: 500,
|
|
54
|
+
messages: msgs(
|
|
55
|
+
`${STYLE}\n\nФАЙЛ ${facts.relPath}:\n\`\`\`\n${src}\n\`\`\`\n\nВІДОМІ ФАКТИ:\n${factsTxt}`,
|
|
56
|
+
`Напиши вміст секції «Поведінка»: ${multi ? 'для кожної публічної функції — один короткий пункт «що вона робить»' : 'нумерований алгоритм у бізнес-термінах'}. Якщо у фактах є свідомі пропуски шляхів — згадай їх там, де доречно (не вигадуй інших «не перевіряє»). НЕ пиши аргументи функцій у дужках, без regex.${facts.internalSymbols?.length ? ` НЕ згадуй за іменами службові функції: ${facts.internalSymbols.join(', ')}.` : ''} Без заголовка.`
|
|
57
|
+
)
|
|
55
58
|
})
|
|
56
59
|
|
|
57
60
|
// API — лише список експортів (без коду)
|
|
58
61
|
if (multi || facts.exports?.some(e => e.desc)) {
|
|
59
62
|
const list = facts.exports.map(e => `- ${e.name}: ${e.desc || '(сформулюй стисло з наміру файлу)'}`).join('\n')
|
|
60
63
|
out.push({
|
|
61
|
-
key: 'api',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
key: 'api',
|
|
65
|
+
numPredict: 320,
|
|
66
|
+
messages: msgs(
|
|
67
|
+
STYLE,
|
|
68
|
+
`Перепиши цей список як стислі маркери «назва — що робить», СВОЇМИ словами (не копіюй дослівно), без типів і сигнатур. Використовуй РІВНО ці назви, не додавай і не прибирай:\n${list}\nБез заголовка.`
|
|
69
|
+
)
|
|
64
70
|
})
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
// Гарантії — лише markers (без коду)
|
|
68
74
|
out.push({
|
|
69
|
-
key: 'guarantees',
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
key: 'guarantees',
|
|
76
|
+
numPredict: 300,
|
|
77
|
+
messages: msgs(
|
|
78
|
+
`${STYLE}\n\nВІДОМІ ФАКТИ:\n${factsTxt}`,
|
|
79
|
+
'Напиши вміст секції «Гарантії поведінки» як маркери-інваріанти СУВОРО на основі ВІДОМИХ ФАКТІВ (read-only, fail-safe, пропуски). Згадуй кеш ЛИШЕ якщо у фактах прямо є «Кешує». Без сигнатур у дужках і без імен внутрішніх структур/Map-ів/кешів. Не вигадуй гарантій, яких немає у фактах. Без заголовка.'
|
|
80
|
+
)
|
|
72
81
|
})
|
|
73
82
|
|
|
74
83
|
return out
|
|
@@ -77,8 +86,10 @@ export function sectionMessages(facts, src) {
|
|
|
77
86
|
/** One-shot messages (база для порівняння). */
|
|
78
87
|
export function oneShotMessages(facts, src) {
|
|
79
88
|
const multi = (facts.exports?.length || 0) > 1
|
|
80
|
-
return msgs(
|
|
81
|
-
|
|
89
|
+
return msgs(
|
|
90
|
+
STYLE,
|
|
91
|
+
`Напиши документацію для файлу. Секції: ## Огляд (1-3 речення), ## Поведінка (нумерований/маркований алгоритм), ${multi ? '## Публічний API (назва + що робить), ' : ''}## Гарантії поведінки.\n\nФАЙЛ ${facts.relPath}:\n\`\`\`\n${src}\n\`\`\``
|
|
92
|
+
)
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/** Лише текст user-промпту для one-shot (для хмарного fallback через Anthropic SDK). */
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* docgen scanner — детермінований обхід проєкту для скілу `docgen`.
|
|
3
|
-
*
|
|
4
|
-
* Друкує JSON-список кодових файлів із відносними `sourcePath`/`docPath`
|
|
5
|
-
* (тека `docs/` поряд із джерелом). Рішення про overwrite/skip приймає скіл —
|
|
6
|
-
* scanner лише лістить і ставить прапор `exists`. LLM/мережі тут немає: уся
|
|
7
|
-
* генерація доки — у субагентах скілу.
|
|
8
|
-
*/
|
|
1
|
+
/** @see ./docs/docgen-scan.md */
|
|
9
2
|
// eslint-disable-next-line unicorn/import-style
|
|
10
3
|
import path from 'node:path'
|
|
11
4
|
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# docgen-extract.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Файл `extractFacts` витягує інформацію про поведінку коду, формуючи об'єкт "факт-лист". Цей факт-лист використовується Stage 1 docgen-конвеєра для створення точкових промптів. Функція є ключовою частиною детермінованого процесу генерації документації, виключаючи використання великих мовних моделей.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
1. **Обробка даних:** Система аналізує код, витягуючи інформацію про експортовані функції, імпорти та їхні описи.
|
|
10
|
+
2. **Ідентифікація відхилень:** Система визначає, чи використовується код для виконання операцій, які можуть призвести до помилок, наприклад, запис у файли, створення директорій, видалення файлів, обробка мережевих запитів або використання try/catch блоків.
|
|
11
|
+
3. **Виявлення пропущених шляхів:** Система виявляє, чи використовується код для обробки певних шляхів файлів, таких як `.github`, `.git`, `node_modules` або `base/`, `ua/`, `.firebase`.
|
|
12
|
+
4. **Визначення кешування:** Система визначає, чи використовується код для кешування даних, наприклад, за допомогою Map або Cache.
|
|
13
|
+
5. **Визначення відсутності обробки помилок:** Система визначає, чи використовується код для обробки помилок, наприклад, за допомогою try/catch блоків.
|
|
14
|
+
6. **Визначення мережевих операцій:** Система визначає, чи використовується код для виконання мережевих операцій, наприклад, за допомогою fetch або axios.
|
|
15
|
+
7. **Вихідні дані:** Система надає вихідні дані у вигляді об'єкта, що містить витягнуту інформацію про код, а також виявлені відхилення та пропущені шляхи.
|
|
16
|
+
|
|
17
|
+
## Публічний API
|
|
18
|
+
|
|
19
|
+
extractFacts — Витягує факти з коду файлу, представляючи їх у вигляді списку.
|
|
20
|
+
|
|
21
|
+
## Гарантії поведінки
|
|
22
|
+
|
|
23
|
+
- Екстрагує з коду список фактів.
|
|
24
|
+
- Повертає об'єкт з витягнутими фактами.
|
|
25
|
+
- Не викликає винятків при невдачі.
|
|
26
|
+
- Кешує результати для одного прогону.
|
|
27
|
+
- Не використовує мережу.
|
|
28
|
+
- Не обробляє файли/каталоги .github, .git, node_modules, base/, ua/, .firebase.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# docgen-gen.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Цей файл генерує документацію на основі коду, використовуючи конвеєр обробки. Він автоматично створює Markdown-файли, що містять опис коду, використовуючи декілька етапів, включаючи вилучення фактів та створення текстових описів. Ця функція є ключовою частиною системи документування, забезпечуючи автоматизований та послідовний процес створення документації.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
1. Модуль визначає основну функцію `generateDoc`, яка приймає код джерела та налаштовує параметри для генерації документації.
|
|
10
|
+
2. Вхідний код обробляється для вилучення ключових фактів про функціональність модуля, включаючи назви функцій та їх призначення.
|
|
11
|
+
3. Вилучені факти використовуються для оцінки якості документації, зокрема, перевіряється, чи опис відповідає реальній поведінці модуля.
|
|
12
|
+
4. Оцінка якості здійснюється за допомогою декількох критеріїв: опис має бути чітким та описовим, а поведінка – узгодженою з кодом.
|
|
13
|
+
5. Якщо оцінка якості низька, використовується хмарний сервіс Claude для перевірки документації та надання рекомендацій щодо покращення.
|
|
14
|
+
6. У випадку, коли оцінка якості висока, генерується документ, який містить огляд модуля, його поведінку, API та гарантії поведінки.
|
|
15
|
+
7. Документація форматується у Markdown, з використанням фіксованого набору заголовків та розділів.
|
|
16
|
+
8. Під час генерації документації враховуються можливі пропуски в інформації про шляхи, що може вплинути на оцінку якості.
|
|
17
|
+
9. Використовується хмарний сервіс Claude для оцінки якості документації та надання рекомендацій щодо покращення.
|
|
18
|
+
10. Генерується документ, який містить огляд модуля, його поведінку, API та гарантії поведінки.
|
|
19
|
+
|
|
20
|
+
## Публічний API
|
|
21
|
+
|
|
22
|
+
- generateDoc — Генерує документацію з файлу, включаючи метадані, токени, оцінки, проблеми та рівень.
|
|
23
|
+
- Routing (sym-threshold) — Розподіляє обробку на основі значення символу, визначаючи рівень (Tier) та тип рефері (Haiku або без хмарного рефері).
|
|
24
|
+
- sym < BORDERLINE_SYM_LOW — Tier 1 без хмарного рефері.
|
|
25
|
+
- sym ∈ [BORDERLINE_SYM_LOW, symThreshold) — Tier 1 з хмарним рефері (Haiku).
|
|
26
|
+
- sym >= symThreshold — Відразу Tier 2.
|
|
27
|
+
- scoreCloud=true — Автоматично запускає хмарний рефері для всіх Tier 1.
|
|
28
|
+
|
|
29
|
+
## Гарантії поведінки
|
|
30
|
+
|
|
31
|
+
- Конвеєр завжди генерує .md-документацію на основі вхідного коду.
|
|
32
|
+
- Конвеєр використовує інверсію керування, де JS-код визначає весь процес.
|
|
33
|
+
- Stage 0 (extractFacts) не впливає на вихідну документацію.
|
|
34
|
+
- Stage 1 (sectionInstructions) генерує точкові промпти для кожної секції коду.
|
|
35
|
+
- Stage 2 (stripSignatures) завжди видаляє інформацію про сигнатури.
|
|
36
|
+
- Stage 2.5 (scoreDoc) оцінює документацію за фактами.
|
|
37
|
+
- Stage 3 (assemble) збирає документацію з фіксованими заголовками та порядком.
|
|
38
|
+
- При низькому балі скорингу (claudeOneShot) використовується хмарний сервіс для перефразування.
|
|
39
|
+
- При сим-значенні нижче BORDERLINE_SYM_LOW, документація генерується локально без використання хмарного рефері.
|
|
40
|
+
- При сим-значенні між BORDERLINE_SYM_LOW та DEFAULT_SYM_THRESHOLD, документація генерується локально та оцінюється за допомогою cloudScoreDoc (Haiku).
|
|
41
|
+
- При сим-значенні, що перевищує DEFAULT_SYM_THRESHOLD, документація генерується безпосередньо, без локальної оцінки
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# docgen-ignore.mjs
|
|
2
|
+
|
|
3
|
+
## Огляд
|
|
4
|
+
|
|
5
|
+
Цей файл містить список глобальних файлів, які `docgen` ігнорує під час генерації документації. Він використовується для визначення, які файли не потрібно аналізувати, забезпечуючи ефективність процесу генерації та запобігаючи включенню нерелевантної інформації. Цей список є основою для predicate, який scanner використовує для визначення, чи потрібно читати певний файл.
|
|
6
|
+
|
|
7
|
+
## Поведінка
|
|
8
|
+
|
|
9
|
+
DOCGEN_IGNORE_GLOBS: визначає список глобів, які `docgen` ігнорує.
|
|
10
|
+
isDocgenIgnored: перевіряє, чи шлях має бути пропущений `docgen`, використовуючи глоби.
|
|
11
|
+
|
|
12
|
+
## Публічний API
|
|
13
|
+
|
|
14
|
+
- DOCGEN_IGNORE_GLOBS — Список glob-ів, які ігнорує `docgen`.
|
|
15
|
+
- isDocgenIgnored — Перевіряє, чи потрібно пропустити шлях під час генерації документації.
|
|
16
|
+
|
|
17
|
+
## Гарантії поведінки
|
|
18
|
+
|
|
19
|
+
- `DOCGEN_IGNORE_GLOBS` завжди ігнорує певний набір шляхів.
|
|
20
|
+
- `isDocgenIgnored` повертає `true` якщо шлях ігнорується.
|
|
21
|
+
- Шляхи `.git` та `node_modules` завжди ігноруються.
|
|
22
|
+
- Файл є read-only.
|
|
23
|
+
- У разі невдачі повертає `false` або `null`.
|
|
24
|
+
- Результат прогону кешується для подальшого використання.
|