@nitra/cursor 5.1.0 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-template/settings.template.json +22 -0
- package/.pi-template/extensions/n-cursor-adr/docs/index.md +15 -9
- package/CHANGELOG.md +18 -1
- package/bin/n-cursor.js +73 -16
- package/docs/stryker.config.md +6 -0
- package/docs/vitest.config.md +6 -0
- package/lib/docs/llm.md +29 -0
- package/lib/docs/models.md +24 -17
- package/lib/docs/omlx.md +32 -0
- package/lib/llm.mjs +137 -0
- package/lib/omlx.mjs +49 -4
- package/package.json +1 -1
- package/rules/abie/docs/fix.md +6 -0
- package/rules/abie/js/docs/applies.md +6 -0
- package/rules/abie/js/docs/env_dns.md +25 -22
- package/rules/abie/js/docs/firebase_hosting.md +6 -0
- package/rules/abie/js/docs/hc_pairing.md +21 -25
- package/rules/abie/js/docs/ua_http_route.md +27 -19
- package/rules/abie/js/docs/ua_node_selector.md +24 -19
- package/rules/abie/lib/docs/enabled.md +13 -7
- package/rules/abie/lib/docs/env-dns.md +9 -3
- package/rules/abie/lib/docs/hc-yaml.md +6 -0
- package/rules/abie/lib/docs/http-route.md +6 -0
- package/rules/abie/lib/docs/k8s-tree.md +6 -0
- package/rules/abie/lib/docs/kustomization-patches.md +6 -0
- package/rules/abie/lib/docs/overlay-paths.md +6 -0
- package/rules/abie/lib/docs/yaml.md +6 -0
- package/rules/adr/docs/fix.md +6 -0
- package/rules/adr/js/docs/hooks.md +29 -244
- package/rules/bun/docs/fix.md +6 -0
- package/rules/bun/js/docs/layout.md +37 -375
- package/rules/capacitor/docs/fix.md +22 -108
- package/rules/capacitor/js/docs/platforms.md +62 -268
- package/rules/changelog/docs/fix.md +6 -0
- package/rules/changelog/js/docs/consistency.md +36 -383
- package/rules/changelog/lib/docs/package-manifest.md +6 -0
- package/rules/ci4/docs/fix.md +23 -165
- package/rules/ci4/js/docs/marksman_config.md +9 -1
- package/rules/docker/docs/fix.md +6 -0
- package/rules/docker/js/docs/lint.md +55 -239
- package/rules/docker/lib/docs/docker-hadolint.md +6 -0
- package/rules/docker/lib/docs/docker-mirror.md +6 -0
- package/rules/docker/lib/docs/docker-native-addon.md +6 -0
- package/rules/docker/lib/docs/docker-nginx-user.md +6 -0
- package/rules/docker/lint/docs/lint.md +9 -1
- package/rules/efes/docs/fix.md +6 -0
- package/rules/feedback/docs/fix.md +21 -131
- package/rules/ga/docs/fix.md +14 -12
- package/rules/ga/js/docs/lint.md +12 -9
- package/rules/ga/js/docs/workflows.md +20 -19
- package/rules/ga/lint/docs/lint.md +6 -0
- package/rules/graphql/docs/fix.md +6 -0
- package/rules/graphql/js/docs/tooling.md +18 -253
- package/rules/graphql/lib/docs/graphql-gql-scan.md +6 -0
- package/rules/hasura/docs/fix.md +18 -111
- package/rules/image-avif/docs/fix.md +6 -0
- package/rules/image-avif/js/docs/avif_generation.md +6 -0
- package/rules/js-bun-db/lib/docs/bun-sql-scan.md +9 -3
- package/rules/js-bun-redis/lib/docs/redis-imports.md +6 -0
- package/rules/js-lint/js/docs/utils_imports.md +6 -0
- package/rules/js-lint-ci/docs/fix.md +7 -1
- package/rules/js-mssql/docs/fix.md +6 -0
- package/rules/js-mssql/lib/docs/mssql-pool-scan.md +6 -0
- package/rules/js-run/docs/fix.md +6 -0
- package/rules/js-run/lib/docs/bunyan-imports.md +6 -0
- package/rules/js-run/lib/docs/check-env-scan.md +6 -0
- package/rules/js-run/lib/docs/conn-file-rules.md +6 -0
- package/rules/js-run/lib/docs/conn-imports-scan.md +6 -0
- package/rules/js-run/lib/docs/promise-settimeout-scan.md +6 -0
- package/rules/js-run/lib/docs/temporal-scan.md +6 -0
- package/rules/k8s/docs/fix.md +6 -0
- package/rules/k8s/lint/docs/lint.md +6 -0
- package/rules/nginx-default-tpl/docs/fix.md +6 -0
- package/rules/npm-module/js/docs/header_doc_pointer.md +7 -0
- package/rules/npm-module/js/header_doc_pointer.mjs +2 -8
- package/rules/php/docs/fix.md +6 -0
- package/rules/php/lint/docs/lint.md +6 -0
- package/rules/python/docs/fix.md +6 -0
- package/rules/python/lint/docs/lint.md +6 -0
- package/rules/rego/lint/docs/lint.md +6 -0
- package/rules/release/docs/change.md +6 -0
- package/rules/release/docs/fix.md +6 -0
- package/rules/release/docs/release.md +6 -0
- package/rules/release/lib/docs/aggregate.md +6 -0
- package/rules/release/lib/docs/change-file.md +6 -0
- package/rules/release/lib/docs/fallback.md +6 -0
- package/rules/rust/lib/docs/has-cargo-toml.md +6 -0
- package/rules/security/docs/fix.md +7 -1
- package/rules/security/js/docs/lint.md +6 -0
- package/rules/style-lint/docs/fix.md +6 -0
- package/rules/tauri/docs/fix.md +6 -0
- package/rules/test/docs/fix.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker-vue-macros-ignorer.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.baseline.md +6 -0
- package/rules/test/js/data/stryker_config/docs/stryker.config.vue.baseline.md +6 -0
- package/rules/test/js/data/vitest_config/docs/vitest.config.baseline.md +6 -0
- package/rules/text/docs/fix.md +6 -0
- package/rules/text/lint/docs/lint.md +6 -0
- package/rules/text/lint/docs/run-dotenv-linter.md +6 -0
- package/rules/text/lint/docs/run-shellcheck.md +6 -0
- package/rules/text/lint/docs/run-v8r.md +6 -0
- package/rules/vue/lib/docs/vue-forbidden-imports.md +6 -0
- package/scripts/coverage-classify/cache.mjs +1 -1
- package/scripts/coverage-classify/docs/apply.md +6 -0
- package/scripts/coverage-classify/docs/cache.md +6 -0
- package/scripts/coverage-classify/docs/prompt.md +6 -0
- package/scripts/coverage-classify/docs/verdict-schema.md +6 -0
- package/scripts/coverage-classify/prompt.mjs +1 -1
- package/scripts/coverage-fix-extract.mjs +1 -1
- package/scripts/coverage-fix.mjs +2 -1
- package/scripts/docs/auto-skills.md +6 -0
- package/scripts/docs/build-agents-commands.md +7 -1
- package/scripts/docs/cli-entry.md +6 -0
- package/scripts/docs/coverage-fix-extract.md +6 -0
- package/scripts/docs/coverage-fix.md +6 -0
- package/scripts/docs/ensure-nitra-cursor-dev-dependencies.md +6 -0
- package/scripts/docs/lint-cli.md +6 -0
- package/scripts/docs/post-tool-use-fix.md +6 -0
- package/scripts/docs/rename-yaml-extensions.md +6 -0
- package/scripts/docs/skills-cli.md +6 -0
- package/scripts/docs/sync-setup-bun-deps-action.md +6 -0
- package/scripts/docs/upgrade-nitra-cursor-and-install.md +6 -0
- package/scripts/docs/worktree-cli.md +6 -0
- package/scripts/lib/docs/assert-project-root.md +6 -0
- package/scripts/lib/docs/check-mdc-template-refs.md +6 -0
- package/scripts/lib/docs/check-reporter.md +6 -0
- package/scripts/lib/docs/diff-added-lines.md +6 -0
- package/scripts/lib/docs/discover-check-rules-from-cursor.md +6 -0
- package/scripts/lib/docs/discover-checkable-rules.md +6 -0
- package/scripts/lib/docs/ensure-tool.md +6 -0
- package/scripts/lib/docs/generated-markdown.md +6 -0
- package/scripts/lib/docs/gha-workflow.md +6 -0
- package/scripts/lib/docs/inline-template-links.md +6 -0
- package/scripts/lib/docs/list-rule-ids.md +6 -0
- package/scripts/lib/docs/load-cursor-config.md +6 -0
- package/scripts/lib/docs/mirror-parity.md +6 -0
- package/scripts/lib/docs/read-n-cursor-config-lite.md +6 -0
- package/scripts/lib/docs/resolve-target-files.md +6 -0
- package/scripts/lib/docs/root-notice.md +6 -0
- package/scripts/lib/docs/rule-meta-helpers.md +6 -0
- package/scripts/lib/docs/rule-meta.md +6 -0
- package/scripts/lib/docs/run-conftest-batch.md +6 -0
- package/scripts/lib/docs/run-lint-step.md +6 -0
- package/scripts/lib/docs/run-rule-cli.md +6 -0
- package/scripts/lib/docs/run-rule.md +6 -0
- package/scripts/lib/docs/run-standard-lint.md +6 -0
- package/scripts/lib/docs/run-standard-rule.md +6 -0
- package/scripts/lib/docs/skill-meta.md +6 -0
- package/scripts/lib/docs/template.md +6 -0
- package/scripts/lib/docs/timing-summary.md +6 -0
- package/scripts/lib/docs/workspaces.md +6 -0
- package/scripts/lib/docs/worktree-notice.md +6 -0
- package/scripts/lib/docs/worktree.md +6 -0
- package/scripts/lib/mirror-parity.mjs +1 -1
- package/scripts/lib/root-notice.mjs +1 -1
- package/scripts/lib/worktree-notice.mjs +5 -5
- package/scripts/lib/worktree.mjs +1 -1
- package/scripts/sync-claude-config.mjs +3 -0
- package/scripts/utils/docs/ast-scan-utils.md +6 -0
- package/scripts/utils/docs/ensure-gitignore-entries.md +6 -0
- package/scripts/utils/docs/find-package-json-paths.md +6 -0
- package/scripts/utils/docs/lock-cache-dir.md +6 -0
- package/scripts/utils/docs/pass.md +6 -0
- package/scripts/utils/docs/resolve-cargo-manifest.md +6 -0
- package/scripts/utils/docs/resolve-cmd.md +6 -0
- package/scripts/utils/docs/resolve-js-root.md +6 -0
- package/scripts/utils/docs/test-helpers.md +6 -0
- package/scripts/utils/docs/walk-cache.md +6 -0
- package/scripts/utils/docs/walkDir.md +6 -0
- package/scripts/utils/docs/worktree-fingerprint.md +6 -0
- package/scripts/utils/resolve-js-root.mjs +1 -1
- package/skills/doc-aggregate/SKILL.md +129 -0
- package/skills/doc-aggregate/js/docgen-ignore.mjs +9 -0
- package/skills/{docgen → doc-aggregate}/js/docgen-scan.mjs +22 -67
- package/skills/doc-aggregate/js/docs/docgen-ignore.md +21 -0
- package/skills/doc-files/SKILL.md +100 -0
- package/skills/doc-files/js/docgen-crc.mjs +164 -0
- package/skills/{docgen → doc-files}/js/docgen-extract-anchors.mjs +48 -13
- package/skills/{docgen → doc-files}/js/docgen-extract.mjs +39 -10
- package/skills/doc-files/js/docgen-files-batch.mjs +181 -0
- package/skills/doc-files/js/docgen-gen.mjs +336 -0
- package/skills/{docgen → doc-files}/js/docgen-prompts.mjs +65 -50
- package/skills/doc-files/js/docgen-scan.mjs +298 -0
- package/skills/doc-files/js/docs/docgen-crc.md +32 -0
- package/skills/doc-files/js/docs/docgen-extract-anchors.md +27 -0
- package/skills/doc-files/js/docs/docgen-extract.md +29 -0
- package/skills/doc-files/js/docs/docgen-files-batch.md +25 -0
- package/skills/doc-files/js/docs/docgen-gen.md +30 -0
- package/skills/doc-files/js/docs/docgen-prompts.md +32 -0
- package/skills/doc-files/js/docs/docgen-scan.md +25 -0
- package/skills/doc-files/js/units-js.mjs +139 -0
- package/skills/doc-files/js/units.mjs +19 -0
- package/skills/doc-files/meta.json +1 -0
- package/skills/fix/js/docs/llm-worker.md +6 -0
- package/skills/fix/js/docs/orchestrator.md +6 -0
- package/skills/fix/js/llm-worker.mjs +3 -3
- package/skills/fix/js/orchestrator.mjs +1 -1
- package/skills/start-check/js/check.mjs +5 -3
- package/skills/start-check/js/docs/check.md +6 -0
- package/skills/docgen/SKILL.md +0 -224
- package/skills/docgen/bench/etalon/firebase_hosting.md +0 -19
- package/skills/docgen/bench/etalon/k8s-tree.md +0 -24
- package/skills/docgen/bench/etalon/overlay-paths.md +0 -24
- package/skills/docgen/js/docgen-batch-omlx.mjs +0 -82
- package/skills/docgen/js/docgen-batch.mjs +0 -95
- package/skills/docgen/js/docgen-compare-pi-vs-direct.mjs +0 -95
- package/skills/docgen/js/docgen-gen.mjs +0 -306
- package/skills/docgen/js/docs/docgen-extract.md +0 -28
- package/skills/docgen/js/docs/docgen-gen.md +0 -41
- package/skills/docgen/js/docs/docgen-ignore.md +0 -24
- package/skills/docgen/js/docs/docgen-prompts.md +0 -24
- package/skills/docgen/js/docs/docgen-scan.md +0 -48
- /package/skills/{docgen → doc-aggregate}/meta.json +0 -0
- /package/skills/{docgen → doc-files}/js/docgen-ignore.mjs +0 -0
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Тимчасовий A/B-batch: docgen Tier 1 через omlx (gemma-4-e2b 4bit на MLX)
|
|
3
|
-
* замість pi/ollama. Перезаписує всі docs/<stem>.md для файлів з sym<4,
|
|
4
|
-
* НЕ ескалює в cloud. Призначення — порівняння якості omlx vs попередньої версії.
|
|
5
|
-
*
|
|
6
|
-
* Запуск: node npm/skills/docgen/js/docgen-batch-omlx.mjs [--limit N] [--from N]
|
|
7
|
-
* --limit N — обробити перші N файлів зі списку sym<4
|
|
8
|
-
* --from N — почати з індексу N (для дозапуску)
|
|
9
|
-
*/
|
|
10
|
-
import { readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
11
|
-
import { dirname, join, resolve } from 'node:path'
|
|
12
|
-
import { fileURLToPath } from 'node:url'
|
|
13
|
-
import { execSync } from 'node:child_process'
|
|
14
|
-
import { env } from 'node:process'
|
|
15
|
-
import { generateDoc } from './docgen-gen.mjs'
|
|
16
|
-
import { extractFacts } from './docgen-extract.mjs'
|
|
17
|
-
|
|
18
|
-
const ROOT = resolve(fileURLToPath(import.meta.url), '../../../../..')
|
|
19
|
-
|
|
20
|
-
const args = process.argv.slice(2)
|
|
21
|
-
const limitIdx = args.indexOf('--limit')
|
|
22
|
-
const limit = limitIdx !== -1 ? Number(args[limitIdx + 1]) : Infinity
|
|
23
|
-
const fromIdx = args.indexOf('--from')
|
|
24
|
-
const from = fromIdx !== -1 ? Number(args[fromIdx + 1]) : 0
|
|
25
|
-
|
|
26
|
-
env.N_CURSOR_DOCGEN_BACKEND = 'omlx'
|
|
27
|
-
|
|
28
|
-
const scanOut = execSync('node npm/bin/n-cursor.js docgen scan', { cwd: ROOT, encoding: 'utf8' })
|
|
29
|
-
const all = JSON.parse(scanOut)
|
|
30
|
-
|
|
31
|
-
const local = []
|
|
32
|
-
for (const f of all) {
|
|
33
|
-
try {
|
|
34
|
-
const src = readFileSync(join(ROOT, f.sourcePath), 'utf8')
|
|
35
|
-
const facts = extractFacts(src, join(ROOT, f.sourcePath))
|
|
36
|
-
const sym = (facts.internalSymbols ?? []).length
|
|
37
|
-
if (sym < 4) local.push({ ...f, sym })
|
|
38
|
-
} catch {
|
|
39
|
-
/* пропускаємо нечитані */
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const slice = local.slice(from, from + limit)
|
|
44
|
-
console.log(`📋 Файлів sym<4 у проєкті: ${local.length}; обробляємо: ${slice.length} (from=${from}, limit=${limit === Infinity ? 'усе' : limit})`)
|
|
45
|
-
console.log(`🤖 Бекенд: omlx → ${env.N_CURSOR_DOCGEN_OMLX_URL ?? 'http://127.0.0.1:8000/v1/chat/completions'}`)
|
|
46
|
-
|
|
47
|
-
const stats = { ok: 0, err: 0, totalMs: 0, scores: [], errors: [] }
|
|
48
|
-
|
|
49
|
-
for (let i = 0; i < slice.length; i++) {
|
|
50
|
-
const f = slice[i]
|
|
51
|
-
const t0 = Date.now()
|
|
52
|
-
const pct = Math.round(((i + 1) / slice.length) * 100)
|
|
53
|
-
process.stdout.write(` [${i + 1}/${slice.length} ${pct}%] sym=${f.sym} ${f.sourcePath} ... `)
|
|
54
|
-
try {
|
|
55
|
-
const result = await generateDoc(join(ROOT, f.sourcePath), {
|
|
56
|
-
symThreshold: 999, // не уходити в cloud за sym
|
|
57
|
-
cloudModel: null // повністю вимкнути cloud-fallback навіть при low det-score
|
|
58
|
-
})
|
|
59
|
-
const docAbs = join(ROOT, f.docPath)
|
|
60
|
-
mkdirSync(dirname(docAbs), { recursive: true })
|
|
61
|
-
writeFileSync(docAbs, result.md)
|
|
62
|
-
const ms = Date.now() - t0
|
|
63
|
-
stats.ok++
|
|
64
|
-
stats.totalMs += ms
|
|
65
|
-
stats.scores.push(result.score ?? 0)
|
|
66
|
-
process.stdout.write(`✓ ${Math.round(ms / 1000)}s score=${result.score ?? '?'} tier=${result.tier}\n`)
|
|
67
|
-
} catch (error) {
|
|
68
|
-
stats.err++
|
|
69
|
-
stats.errors.push({ path: f.sourcePath, msg: error.message })
|
|
70
|
-
process.stdout.write(`✗ ${error.message}\n`)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const avgScore = stats.scores.length ? Math.round(stats.scores.reduce((a, b) => a + b, 0) / stats.scores.length) : 0
|
|
75
|
-
console.log(`\n${'─'.repeat(60)}`)
|
|
76
|
-
console.log(`✓ OK: ${stats.ok} ✗ Err: ${stats.err}`)
|
|
77
|
-
console.log(` Сумарний час: ${Math.round(stats.totalMs / 1000)}s; середній на файл: ${stats.ok ? Math.round(stats.totalMs / stats.ok / 1000) : 0}s`)
|
|
78
|
-
console.log(` Середній det-score: ${avgScore}`)
|
|
79
|
-
if (stats.errors.length) {
|
|
80
|
-
console.log('Помилки:')
|
|
81
|
-
for (const e of stats.errors) console.log(` - ${e.path}: ${e.msg}`)
|
|
82
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch docgen для відсутніх файлів проєкту.
|
|
3
|
-
* sym < 4 → gemma3:4b orchestrated (local)
|
|
4
|
-
* sym ≥ 4 → Claude Sonnet (cloud, via generateDoc pre-routing)
|
|
5
|
-
*/
|
|
6
|
-
import { readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
7
|
-
import { dirname, join, resolve } from 'node:path'
|
|
8
|
-
import { fileURLToPath } from 'node:url'
|
|
9
|
-
import { generateDoc } from './docgen-gen.mjs'
|
|
10
|
-
import { extractFacts } from './docgen-extract.mjs'
|
|
11
|
-
import { execSync } from 'node:child_process'
|
|
12
|
-
|
|
13
|
-
const ROOT = resolve(fileURLToPath(import.meta.url), '../../../../..')
|
|
14
|
-
|
|
15
|
-
// 1. Отримати список відсутніх файлів
|
|
16
|
-
const scanOut = execSync('node npm/bin/n-cursor.js docgen scan', { cwd: ROOT, encoding: 'utf8' })
|
|
17
|
-
const allFiles = JSON.parse(scanOut)
|
|
18
|
-
const missing = allFiles.filter(x => !x.exists)
|
|
19
|
-
|
|
20
|
-
console.log(`\n📋 Файлів для генерації: ${missing.length}`)
|
|
21
|
-
|
|
22
|
-
// 2. Розкласти по тирах
|
|
23
|
-
const cloud = [],
|
|
24
|
-
local = []
|
|
25
|
-
for (const f of missing) {
|
|
26
|
-
try {
|
|
27
|
-
const src = readFileSync(join(ROOT, f.sourcePath), 'utf8')
|
|
28
|
-
const facts = extractFacts(src, join(ROOT, f.sourcePath))
|
|
29
|
-
const sym = (facts.internalSymbols ?? []).length
|
|
30
|
-
if (sym >= 4) cloud.push({ ...f, sym })
|
|
31
|
-
else local.push({ ...f, sym })
|
|
32
|
-
} catch {
|
|
33
|
-
local.push({ ...f, sym: 0 })
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
console.log(` Local (sym<4): ${local.length}`)
|
|
38
|
-
console.log(` Cloud (sym≥4): ${cloud.length}`)
|
|
39
|
-
|
|
40
|
-
const stats = { ok: 0, err: 0, localOk: 0, cloudOk: 0, errors: [] }
|
|
41
|
-
|
|
42
|
-
// 3. Cloud файли (sym≥4) — generateDoc auto-routes до Claude
|
|
43
|
-
console.log('\n☁️ Cloud tier...')
|
|
44
|
-
for (const f of cloud) {
|
|
45
|
-
const t0 = Date.now()
|
|
46
|
-
try {
|
|
47
|
-
const result = await generateDoc(join(ROOT, f.sourcePath), { symThreshold: 4 })
|
|
48
|
-
const docAbs = join(ROOT, f.docPath)
|
|
49
|
-
mkdirSync(dirname(docAbs), { recursive: true })
|
|
50
|
-
writeFileSync(docAbs, result.md)
|
|
51
|
-
stats.ok++
|
|
52
|
-
stats.cloudOk++
|
|
53
|
-
console.log(` ✓ ${f.sourcePath} (sym=${f.sym}, ${Math.round((Date.now() - t0) / 1000)}s)`)
|
|
54
|
-
} catch (error) {
|
|
55
|
-
stats.err++
|
|
56
|
-
stats.errors.push(f.sourcePath)
|
|
57
|
-
console.error(` ✗ ${f.sourcePath}: ${error.message}`)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 4. Local файли (sym<4) — gemma3:4b orchestrated
|
|
62
|
-
console.log('\n💻 Local tier...')
|
|
63
|
-
let done = 0
|
|
64
|
-
for (const f of local) {
|
|
65
|
-
done++
|
|
66
|
-
const t0 = Date.now()
|
|
67
|
-
const pct = Math.round((done / local.length) * 100)
|
|
68
|
-
process.stdout.write(` [${done}/${local.length} ${pct}%] ${f.sourcePath} ... `)
|
|
69
|
-
try {
|
|
70
|
-
const result = await generateDoc(join(ROOT, f.sourcePath), {
|
|
71
|
-
mode: 'orchestrated',
|
|
72
|
-
symThreshold: 999 // force local
|
|
73
|
-
})
|
|
74
|
-
const docAbs = join(ROOT, f.docPath)
|
|
75
|
-
mkdirSync(dirname(docAbs), { recursive: true })
|
|
76
|
-
writeFileSync(docAbs, result.md)
|
|
77
|
-
stats.ok++
|
|
78
|
-
stats.localOk++
|
|
79
|
-
process.stdout.write(`✓ ${Math.round((Date.now() - t0) / 1000)}s score=${result.score ?? '?'}\n`)
|
|
80
|
-
} catch (error) {
|
|
81
|
-
stats.err++
|
|
82
|
-
stats.errors.push(f.sourcePath)
|
|
83
|
-
process.stdout.write(`✗ ${error.message}\n`)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// 5. Підсумок
|
|
88
|
-
console.log(`\n${'─'.repeat(50)}`)
|
|
89
|
-
console.log(`✓ OK: ${stats.ok} ✗ Err: ${stats.err}`)
|
|
90
|
-
console.log(` 💻 Local (gemma3:4b): ${stats.localOk} файлів`)
|
|
91
|
-
console.log(` ☁️ Cloud (Claude/pi): ${stats.cloudOk} файлів`)
|
|
92
|
-
if (stats.errors.length > 0) {
|
|
93
|
-
console.log('Помилки:')
|
|
94
|
-
for (const e of stats.errors) console.log(` - ${e}`)
|
|
95
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A/B: docgen Tier 1 через pi cli (з omlx-провайдером у ~/.pi/agent/models.json)
|
|
3
|
-
* vs прямий callOmlxMessages (`N_CURSOR_DOCGEN_BACKEND=omlx`).
|
|
4
|
-
*
|
|
5
|
-
* Однаковий 8-сет файлів, однаковий оркестратор (E1+E2+E3+E4), різний backend.
|
|
6
|
-
* Пише в /tmp/docgen-compare/{pi,direct}/<idx>-<stem>.md і збирає метрики.
|
|
7
|
-
*
|
|
8
|
-
* Запуск: node npm/skills/docgen/js/docgen-compare-pi-vs-direct.mjs [--from N] [--limit N]
|
|
9
|
-
*/
|
|
10
|
-
import { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs'
|
|
11
|
-
import { join, resolve, basename } from 'node:path'
|
|
12
|
-
import { fileURLToPath } from 'node:url'
|
|
13
|
-
import { execSync } from 'node:child_process'
|
|
14
|
-
import { env } from 'node:process'
|
|
15
|
-
import { generateDoc } from './docgen-gen.mjs'
|
|
16
|
-
import { extractFacts } from './docgen-extract.mjs'
|
|
17
|
-
|
|
18
|
-
const ROOT = resolve(fileURLToPath(import.meta.url), '../../../../..')
|
|
19
|
-
const TMP = '/tmp/docgen-compare'
|
|
20
|
-
|
|
21
|
-
const args = process.argv.slice(2)
|
|
22
|
-
const limitIdx = args.indexOf('--limit')
|
|
23
|
-
const limit = limitIdx !== -1 ? Number(args[limitIdx + 1]) : 8
|
|
24
|
-
const fromIdx = args.indexOf('--from')
|
|
25
|
-
const from = fromIdx !== -1 ? Number(args[fromIdx + 1]) : 1
|
|
26
|
-
|
|
27
|
-
const scanOut = execSync('node npm/bin/n-cursor.js docgen scan', { cwd: ROOT, encoding: 'utf8' })
|
|
28
|
-
const all = JSON.parse(scanOut)
|
|
29
|
-
|
|
30
|
-
const local = []
|
|
31
|
-
for (const f of all) {
|
|
32
|
-
try {
|
|
33
|
-
const src = readFileSync(join(ROOT, f.sourcePath), 'utf8')
|
|
34
|
-
const facts = extractFacts(src, join(ROOT, f.sourcePath))
|
|
35
|
-
const sym = (facts.internalSymbols ?? []).length
|
|
36
|
-
if (sym < 4) local.push({ ...f, sym })
|
|
37
|
-
} catch {}
|
|
38
|
-
}
|
|
39
|
-
const slice = local.slice(from, from + limit)
|
|
40
|
-
|
|
41
|
-
mkdirSync(join(TMP, 'pi'), { recursive: true })
|
|
42
|
-
mkdirSync(join(TMP, 'direct'), { recursive: true })
|
|
43
|
-
|
|
44
|
-
async function runBackendAsync(kind) {
|
|
45
|
-
if (kind === 'direct') env.N_CURSOR_DOCGEN_BACKEND = 'omlx'
|
|
46
|
-
else delete env.N_CURSOR_DOCGEN_BACKEND
|
|
47
|
-
const out = { ok: 0, err: 0, totalMs: 0, scores: [], lengths: [], errors: [], times: [] }
|
|
48
|
-
console.log(`\n══════ Backend: ${kind} ══════`)
|
|
49
|
-
for (let i = 0; i < slice.length; i++) {
|
|
50
|
-
const f = slice[i]
|
|
51
|
-
const t0 = Date.now()
|
|
52
|
-
const stem = basename(f.sourcePath).replace(/\.[^.]+$/, '')
|
|
53
|
-
const destFile = join(TMP, kind, `${String(i + 1).padStart(2, '0')}-${stem}.md`)
|
|
54
|
-
process.stdout.write(` [${i + 1}/${slice.length}] sym=${f.sym} ${f.sourcePath} ... `)
|
|
55
|
-
try {
|
|
56
|
-
const r = await generateDoc(join(ROOT, f.sourcePath), { symThreshold: 999, cloudModel: null })
|
|
57
|
-
writeFileSync(destFile, r.md)
|
|
58
|
-
const ms = Date.now() - t0
|
|
59
|
-
out.ok++
|
|
60
|
-
out.totalMs += ms
|
|
61
|
-
out.times.push(ms)
|
|
62
|
-
out.scores.push(r.score ?? 0)
|
|
63
|
-
out.lengths.push(r.md.length)
|
|
64
|
-
process.stdout.write(`✓ ${Math.round(ms / 1000)}s score=${r.score ?? '?'} chars=${r.md.length}\n`)
|
|
65
|
-
} catch (error) {
|
|
66
|
-
out.err++
|
|
67
|
-
out.errors.push({ path: f.sourcePath, msg: error.message })
|
|
68
|
-
process.stdout.write(`✗ ${error.message}\n`)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return out
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const direct = await runBackendAsync('direct')
|
|
75
|
-
const pi = await runBackendAsync('pi')
|
|
76
|
-
|
|
77
|
-
function avg(a) { return a.length ? Math.round(a.reduce((x, y) => x + y, 0) / a.length) : 0 }
|
|
78
|
-
function median(a) {
|
|
79
|
-
if (!a.length) return 0
|
|
80
|
-
const s = a.toSorted((x, y) => x - y)
|
|
81
|
-
return s[Math.floor(s.length / 2)]
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const report = {
|
|
85
|
-
files: slice.map(f => f.sourcePath),
|
|
86
|
-
direct: { ok: direct.ok, err: direct.err, avgMs: avg(direct.times), medianMs: median(direct.times), avgScore: avg(direct.scores), avgChars: avg(direct.lengths), totalSec: Math.round(direct.totalMs / 1000) },
|
|
87
|
-
pi: { ok: pi.ok, err: pi.err, avgMs: avg(pi.times), medianMs: median(pi.times), avgScore: avg(pi.scores), avgChars: avg(pi.lengths), totalSec: Math.round(pi.totalMs / 1000) }
|
|
88
|
-
}
|
|
89
|
-
writeFileSync(join(TMP, 'report.json'), JSON.stringify(report, null, 2))
|
|
90
|
-
|
|
91
|
-
console.log(`\n${'─'.repeat(60)}\nA/B SUMMARY (${slice.length} файлів, той самий оркестратор)\n${'─'.repeat(60)}`)
|
|
92
|
-
console.log(`Backend | ok | err | avg s | median s | avg score | avg chars | total s`)
|
|
93
|
-
console.log(`direct (curl) | ${direct.ok} | ${direct.err} | ${Math.round(report.direct.avgMs / 1000)} | ${Math.round(report.direct.medianMs / 1000)} | ${report.direct.avgScore} | ${report.direct.avgChars} | ${report.direct.totalSec}`)
|
|
94
|
-
console.log(`pi cli | ${pi.ok} | ${pi.err} | ${Math.round(report.pi.avgMs / 1000)} | ${Math.round(report.pi.medianMs / 1000)} | ${report.pi.avgScore} | ${report.pi.avgChars} | ${report.pi.totalSec}`)
|
|
95
|
-
console.log(`\nФайли: ${TMP}/{direct,pi}/<idx>-<stem>.md\nReport: ${TMP}/report.json`)
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/** @see ./docs/docgen-gen.md */
|
|
2
|
-
import { readFileSync } from 'node:fs'
|
|
3
|
-
import { basename } from 'node:path'
|
|
4
|
-
import { spawnSync } from 'node:child_process'
|
|
5
|
-
import { env } from 'node:process'
|
|
6
|
-
import { resolveModel } from '../../../lib/models.mjs'
|
|
7
|
-
import { callOmlx } from '../../../lib/omlx.mjs'
|
|
8
|
-
import { extractFacts } from './docgen-extract.mjs'
|
|
9
|
-
import { extractAnchors } from './docgen-extract-anchors.mjs'
|
|
10
|
-
import { oneShotMessages, sectionMessages, criticMessages, refineMessages, guaranteesFromMarkers } from './docgen-prompts.mjs'
|
|
11
|
-
|
|
12
|
-
const QUALITY_THRESHOLD = 70
|
|
13
|
-
|
|
14
|
-
/** Прибирає ```-обгортку й випадковий провідний `##`-заголовок із секції. */
|
|
15
|
-
function stripSection(text) {
|
|
16
|
-
let t = text.trim()
|
|
17
|
-
if (t.startsWith('```')) {
|
|
18
|
-
t = t
|
|
19
|
-
.replace(/^```[a-z]*\n?/, '')
|
|
20
|
-
.replace(/\n?```\s*$/, '')
|
|
21
|
-
.trim()
|
|
22
|
-
}
|
|
23
|
-
t = t.replace(/^#{1,6}\s+.*\n+/, '') // зрізати випадковий заголовок
|
|
24
|
-
return t.trim()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Stage 2 (детермінований лінт, 0 токенів): зрізає сигнатури `name(args)` → `name`.
|
|
29
|
-
* Два проходи — щоб зняти вкладені виклики на кшталт `check(cwd = process.cwd())`.
|
|
30
|
-
* Не чіпає дужки без ідентифікатора перед ними (напр. `(abie.mdc)`, «(наприклад)»).
|
|
31
|
-
*/
|
|
32
|
-
function stripSignatures(text) {
|
|
33
|
-
let t = text
|
|
34
|
-
for (let i = 0; i < 2; i++) t = t.replaceAll(/([`\w$.]+)\([^()]*\)/g, '$1')
|
|
35
|
-
return t
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Розбиває md на секції за ## заголовками → { огляд, поведінка, api, гарантіїповедінки, … } */
|
|
39
|
-
function parseSections(md) {
|
|
40
|
-
const result = {}
|
|
41
|
-
let cur = null
|
|
42
|
-
for (const line of md.split('\n')) {
|
|
43
|
-
const m = line.match(/^##\s+(.+)/)
|
|
44
|
-
if (m) {
|
|
45
|
-
cur = m[1].toLowerCase().replaceAll(/[^а-яіїєґa-z0-9]/gi, '')
|
|
46
|
-
result[cur] = ''
|
|
47
|
-
} else if (cur) result[cur] += line + '\n'
|
|
48
|
-
}
|
|
49
|
-
return result
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Stage 2.5 — детермінований скоринг (0 токенів): перевіряє вихід проти фактів.
|
|
54
|
-
* @returns {{ score: number, issues: string[] }}
|
|
55
|
-
*/
|
|
56
|
-
function scoreDoc(md, facts) {
|
|
57
|
-
const s = parseSections(md)
|
|
58
|
-
let score = 100
|
|
59
|
-
const issues = []
|
|
60
|
-
|
|
61
|
-
if (!s['огляд']) {
|
|
62
|
-
score -= 25
|
|
63
|
-
issues.push('no-overview')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const behavior = s['поведінка'] ?? ''
|
|
67
|
-
if (behavior.length < 60) {
|
|
68
|
-
score -= 20
|
|
69
|
-
issues.push('short-behavior')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const guarantees = s['гарантіїповедінки'] ?? ''
|
|
73
|
-
// Будь-яка згадка "кеш" у Гарантіях коли файл не кешує — галюцинація
|
|
74
|
-
// Негація: "не кешує", "не має кешування", "без кешування", "немає кешу"
|
|
75
|
-
const cacheHit = /кеш/i.test(guarantees) && !/(?:не|без)\s+(?:\S+\s+)?кеш|немає\s+кеш/i.test(guarantees)
|
|
76
|
-
if (!facts.markers?.caches && cacheHit) {
|
|
77
|
-
score -= 20
|
|
78
|
-
issues.push('cache-hallucination')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Перевіряємо лише бектік-обгорнуті імена (`sym`) — уникаємо substring false positives
|
|
82
|
-
const hasName = (text, sym) => text.includes('`' + sym + '`')
|
|
83
|
-
for (const sym of facts.internalSymbols ?? []) {
|
|
84
|
-
const inDoc = hasName(guarantees, sym) || hasName(s['огляд'] ?? '', sym) || hasName(s['поведінка'] ?? '', sym)
|
|
85
|
-
if (inDoc) {
|
|
86
|
-
score -= 10
|
|
87
|
-
issues.push(`internal-name:${sym}`)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { score: Math.max(0, score), issues }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* omlx-бекенд: справжні OpenAI-сумісні messages (system+user збереженi).
|
|
96
|
-
* Вмикається `N_CURSOR_DOCGEN_BACKEND=omlx`. Делегує у спільний `callOmlx`
|
|
97
|
-
* (npm/lib/omlx.mjs) з docgen-специфічними env-дефолтами URL/моделі.
|
|
98
|
-
*/
|
|
99
|
-
function callOmlxMessages(messages, model, timeoutMs, temperature = 0.2) {
|
|
100
|
-
return callOmlx(messages, model, {
|
|
101
|
-
url: env.N_CURSOR_DOCGEN_OMLX_URL,
|
|
102
|
-
timeoutMs,
|
|
103
|
-
temperature,
|
|
104
|
-
fallbackModel: env.N_CURSOR_DOCGEN_OMLX_MODEL
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Універсальний виклик LLM за повним messages-масивом.
|
|
110
|
-
* - omlx: шле messages напряму (system збережено)
|
|
111
|
-
* - pi: конкатенує message.content (pi приймає лише plain prompt)
|
|
112
|
-
*/
|
|
113
|
-
function callLlm(messages, model, timeoutMs, temperature = 0.2) {
|
|
114
|
-
if (env.N_CURSOR_DOCGEN_BACKEND === 'omlx') return callOmlxMessages(messages, model, timeoutMs, temperature)
|
|
115
|
-
const prompt = messages.map(m => m.content).join('\n\n')
|
|
116
|
-
const modelArgs = model ? ['--model', model] : []
|
|
117
|
-
const r = spawnSync('pi', ['-p', prompt, ...modelArgs, '--no-session', '--mode', 'text', '--no-tools'], {
|
|
118
|
-
encoding: 'utf8',
|
|
119
|
-
timeout: timeoutMs
|
|
120
|
-
})
|
|
121
|
-
if (r.error) throw new Error(`pi error: ${r.error.message}`)
|
|
122
|
-
if (r.status !== 0) throw new Error(`pi exit ${r.status}: ${r.stderr?.slice(0, 300) ?? ''}`)
|
|
123
|
-
return r.stdout?.trim() ?? ''
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* E2 — один цикл critique→refine на секцію.
|
|
128
|
-
* Повертає або уточнену чорнетку, або оригінал якщо критик повідомив NONE.
|
|
129
|
-
*/
|
|
130
|
-
function critiqueRefineSection(sectionKey, draft, facts, anchors, model, timeoutMs) {
|
|
131
|
-
const critique = callLlm(criticMessages(sectionKey, draft, facts, anchors), model, timeoutMs).trim()
|
|
132
|
-
if (!critique || /^\s*NONE\s*$/i.test(critique) || critique.length < 12) return draft
|
|
133
|
-
const refined = callLlm(refineMessages(sectionKey, draft, critique, facts, anchors), model, timeoutMs).trim()
|
|
134
|
-
return stripSignatures(stripSection(refined)) || draft
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Чи треба refine для секції API: тільки якщо є >1 експорту і всі desc-и порожні
|
|
139
|
-
* (саме там модель схильна писати «застосовує логіку до файлу»).
|
|
140
|
-
*/
|
|
141
|
-
function apiNeedsRefine(facts) {
|
|
142
|
-
const exps = facts.exports ?? []
|
|
143
|
-
if (exps.length <= 1) return false
|
|
144
|
-
return exps.every(e => !e.desc)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** One-shot: один виклик LLM на весь документ. */
|
|
148
|
-
function piOneShot(facts, src, model, timeoutMs = 120_000) {
|
|
149
|
-
const text = callLlm(oneShotMessages(facts, src), model, timeoutMs)
|
|
150
|
-
let md = stripSignatures(stripSection(text))
|
|
151
|
-
if (!md.startsWith('#')) md = `# ${basename(facts.relPath)}\n\n${md}`
|
|
152
|
-
return { md: md + '\n', genTok: 0 }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Stage 3: фіксовані заголовки у фіксованому порядку. */
|
|
156
|
-
function assemble(stem, sections) {
|
|
157
|
-
const order = [
|
|
158
|
-
['overview', '## Огляд'],
|
|
159
|
-
['behavior', '## Поведінка'],
|
|
160
|
-
['api', '## Публічний API'],
|
|
161
|
-
['guarantees', '## Гарантії поведінки']
|
|
162
|
-
]
|
|
163
|
-
const parts = [`# ${stem}`]
|
|
164
|
-
for (const [key, title] of order) {
|
|
165
|
-
const body = sections[key]
|
|
166
|
-
if (body && body.trim()) parts.push(`${title}\n\n${body.trim()}`)
|
|
167
|
-
}
|
|
168
|
-
return parts.join('\n\n') + '\n'
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Orchestrated: N окремих pi-викликів, по одному на секцію.
|
|
173
|
-
* Код потрапляє лише в `behavior`; решта секцій — на мінімальному факт-листі.
|
|
174
|
-
*/
|
|
175
|
-
function piOrchestrated(facts, src, model, timeoutMs, { anchors = null, temperature = 0.2 } = {}) {
|
|
176
|
-
const sections = {}
|
|
177
|
-
const anc = anchors ?? extractAnchors(src)
|
|
178
|
-
// E3: «Гарантії» — детермінований шаблон з markers (0 LLM-запитів, 0 generic-фраз)
|
|
179
|
-
sections.guarantees = guaranteesFromMarkers(facts)
|
|
180
|
-
for (const s of sectionMessages(facts, src, anc)) {
|
|
181
|
-
if (s.key === 'guarantees') continue // вже згенеровано детерміновано
|
|
182
|
-
let draft = stripSignatures(stripSection(callLlm(s.messages, model, timeoutMs, temperature)))
|
|
183
|
-
// E2 + E3: critique→refine лише для секцій, де gemma-4 зриває на generic
|
|
184
|
-
if (s.key === 'overview' || (s.key === 'api' && apiNeedsRefine(facts))) {
|
|
185
|
-
draft = critiqueRefineSection(s.key, draft, facts, anc, model, timeoutMs)
|
|
186
|
-
}
|
|
187
|
-
sections[s.key] = draft
|
|
188
|
-
}
|
|
189
|
-
return { md: assemble(basename(facts.relPath), sections), genTok: 0 }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
/** Файли з sym ≥ цього значення одразу йдуть у Tier 2 (без Tier 1 проходу). */
|
|
195
|
-
const DEFAULT_SYM_THRESHOLD = 4
|
|
196
|
-
/** Максимальний час Tier 1 генерації на один файл перед ескалацією у Tier 2. */
|
|
197
|
-
const LOCAL_TIMEOUT_MS = 5 * 60 * 1000
|
|
198
|
-
/** Дефолтна Tier 1 модель: N_CURSOR_DOCGEN_MODEL → resolveModel('min'). */
|
|
199
|
-
const DEFAULT_LOCAL_MODEL = env.N_CURSOR_DOCGEN_MODEL ?? resolveModel('min')
|
|
200
|
-
/** Дефолтна Tier 2 модель: N_CURSOR_DOCGEN_CLOUD_MODEL → resolveModel('avg'). */
|
|
201
|
-
const DEFAULT_CLOUD_MODEL = env.N_CURSOR_DOCGEN_CLOUD_MODEL ?? resolveModel('avg')
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Головний API: файл → { md, genTok, ms, score, issues, tier }.
|
|
205
|
-
*
|
|
206
|
-
* Routing (sym-threshold):
|
|
207
|
-
* sym < symThreshold → Tier 1 pi(resolveModel('min'), timeout=5хв) + det-scorer
|
|
208
|
-
* → timeout або det-score < threshold → Tier 2
|
|
209
|
-
* sym >= symThreshold → Pre-routing одразу Tier 2
|
|
210
|
-
*/
|
|
211
|
-
export async function generateDoc(
|
|
212
|
-
file,
|
|
213
|
-
{
|
|
214
|
-
model = DEFAULT_LOCAL_MODEL,
|
|
215
|
-
cloudModel = DEFAULT_CLOUD_MODEL,
|
|
216
|
-
threshold = QUALITY_THRESHOLD,
|
|
217
|
-
symThreshold = DEFAULT_SYM_THRESHOLD
|
|
218
|
-
} = {}
|
|
219
|
-
) {
|
|
220
|
-
const src = readFileSync(file, 'utf8')
|
|
221
|
-
const facts = extractFacts(src, file)
|
|
222
|
-
const t0 = Date.now()
|
|
223
|
-
|
|
224
|
-
// Pre-routing: складні файли (sym ≥ symThreshold) → одразу Tier 2
|
|
225
|
-
const complexity = facts.internalSymbols?.length ?? 0
|
|
226
|
-
if (complexity >= symThreshold && cloudModel) {
|
|
227
|
-
const r2 = piOneShot(facts, src, cloudModel)
|
|
228
|
-
return { ...r2, ms: Date.now() - t0, score: null, issues: [`pre-routed:sym=${complexity}`], tier: 2, model: cloudModel }
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Tier 1: pi orchestrated (секція за секцією), timeout на секцію = LOCAL_TIMEOUT_MS
|
|
232
|
-
// facts.unsupported → one-shot (структура файлу нестандартна)
|
|
233
|
-
let r
|
|
234
|
-
const anchors = facts.unsupported ? null : extractAnchors(src)
|
|
235
|
-
try {
|
|
236
|
-
r = facts.unsupported
|
|
237
|
-
? piOneShot(facts, src, model, LOCAL_TIMEOUT_MS)
|
|
238
|
-
: piOrchestrated(facts, src, model, LOCAL_TIMEOUT_MS, { anchors })
|
|
239
|
-
} catch (error) {
|
|
240
|
-
if (cloudModel) {
|
|
241
|
-
const r2 = piOneShot(facts, src, cloudModel)
|
|
242
|
-
return { ...r2, ms: Date.now() - t0, score: null, issues: [`tier1-error: ${error.message}`], tier: 2, model: cloudModel }
|
|
243
|
-
}
|
|
244
|
-
throw error
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Stage 2.5: детермінований скоринг (0 токенів) — gate перед Tier 2
|
|
248
|
-
let { score: detScore, issues: detIssues } = scoreDoc(r.md, facts)
|
|
249
|
-
|
|
250
|
-
// E4: best-of-N. Якщо score нижчий за threshold і немає cloud-fallback — спроба
|
|
251
|
-
// ще раз з вищою температурою, керуємо через env (повторні прогони коштовні).
|
|
252
|
-
if (detScore < threshold && !cloudModel && !facts.unsupported && env.N_CURSOR_DOCGEN_BEST_OF !== '0') {
|
|
253
|
-
try {
|
|
254
|
-
const r2 = piOrchestrated(facts, src, model, LOCAL_TIMEOUT_MS, { anchors, temperature: 0.5 })
|
|
255
|
-
const s2 = scoreDoc(r2.md, facts)
|
|
256
|
-
if (s2.score > detScore) {
|
|
257
|
-
r = r2
|
|
258
|
-
detScore = s2.score
|
|
259
|
-
detIssues = [...s2.issues, 'best-of-2:retry-won']
|
|
260
|
-
} else {
|
|
261
|
-
detIssues = [...detIssues, 'best-of-2:retry-lost']
|
|
262
|
-
}
|
|
263
|
-
} catch (error) {
|
|
264
|
-
detIssues = [...detIssues, `best-of-2:retry-error: ${error.message}`]
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (detScore < threshold && cloudModel) {
|
|
269
|
-
const r2 = piOneShot(facts, src, cloudModel)
|
|
270
|
-
return { ...r2, ms: Date.now() - t0, score: detScore, issues: detIssues, tier: 2, model: cloudModel }
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return { ...r, ms: Date.now() - t0, score: detScore, issues: detIssues, tier: 1, model }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// CLI: node docgen-gen.mjs <file> [--model <m>] [--sym-threshold N] [--tier-only]
|
|
277
|
-
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
278
|
-
if (isRunAsCli(import.meta.url)) {
|
|
279
|
-
const args = process.argv.slice(2)
|
|
280
|
-
const file = args.find(a => !a.startsWith('--'))
|
|
281
|
-
const tierOnly = args.includes('--tier-only')
|
|
282
|
-
const mi = args.indexOf('--model')
|
|
283
|
-
const model = mi !== -1 ? args[mi + 1] : DEFAULT_LOCAL_MODEL
|
|
284
|
-
const si = args.indexOf('--sym-threshold')
|
|
285
|
-
const symThreshold = si !== -1 ? Number(args[si + 1]) : DEFAULT_SYM_THRESHOLD
|
|
286
|
-
if (!file) {
|
|
287
|
-
console.error('Usage: node docgen-gen.mjs <file> [--model <m>] [--sym-threshold N] [--tier-only]')
|
|
288
|
-
process.exit(1)
|
|
289
|
-
}
|
|
290
|
-
if (tierOnly) {
|
|
291
|
-
const src = readFileSync(file, 'utf8')
|
|
292
|
-
const facts = extractFacts(src, file)
|
|
293
|
-
const sym = facts.internalSymbols?.length ?? 0
|
|
294
|
-
const icon = sym >= symThreshold ? '☁️ ' : '💻'
|
|
295
|
-
const label =
|
|
296
|
-
sym >= symThreshold
|
|
297
|
-
? `Tier 2 cloud (sym=${sym} ≥ ${symThreshold}, pre-routed)`
|
|
298
|
-
: `Tier 1 local (sym=${sym} < ${symThreshold})`
|
|
299
|
-
process.stdout.write(`${icon} ${label} | ${file}\n`)
|
|
300
|
-
process.exit(0)
|
|
301
|
-
}
|
|
302
|
-
const r = await generateDoc(file, { model, symThreshold })
|
|
303
|
-
const issuesTxt = r.issues?.length ? ` issues=${r.issues.join(',')}` : ''
|
|
304
|
-
process.stderr.write(`[tier${r.tier} pi-orchestrated] ${r.ms}ms / score=${r.score}${issuesTxt}\n`)
|
|
305
|
-
process.stdout.write(r.md)
|
|
306
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
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.
|
|
@@ -1,41 +0,0 @@
|
|
|
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, документація генерується безпосередньо, без локальної оцінки
|