@nitra/cursor 12.15.0 → 12.16.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/n-cursor.js +2 -11
  3. package/lib/docs/index.md +9 -6
  4. package/lib/docs/pi-agent-fix.md +28 -0
  5. package/lib/docs/pi-agent-skill.md +36 -0
  6. package/lib/docs/pi-model-tiers.md +46 -0
  7. package/lib/docs/pi-one-shot.md +34 -0
  8. package/lib/docs/pi-telemetry-store.md +33 -0
  9. package/lib/docs/pi-trace.md +27 -0
  10. package/lib/docs/pi-write-guard.md +32 -0
  11. package/lib/pi-agent-fix.mjs +253 -0
  12. package/lib/pi-agent-skill.mjs +181 -0
  13. package/lib/pi-model-tiers.mjs +109 -0
  14. package/lib/pi-one-shot.mjs +129 -0
  15. package/lib/pi-telemetry-store.mjs +0 -0
  16. package/lib/pi-trace.mjs +40 -0
  17. package/lib/pi-write-guard.mjs +147 -0
  18. package/package.json +5 -1
  19. package/rules/bun/docs/main.md +7 -6
  20. package/rules/doc-files/js/docgen-files-batch.mjs +20 -5
  21. package/rules/doc-files/js/docgen-gen.mjs +42 -25
  22. package/rules/doc-files/js/docgen-judge-measure.mjs +16 -13
  23. package/rules/doc-files/js/docgen-judge.mjs +11 -9
  24. package/rules/doc-files/js/docs/docgen-files-batch.md +3 -20
  25. package/rules/doc-files/js/docs/docgen-gen.md +3 -20
  26. package/rules/doc-files/js/docs/docgen-judge-measure.md +3 -18
  27. package/rules/doc-files/js/docs/docgen-judge.md +3 -22
  28. package/rules/npm-module/js/docs/skill_meta.md +22 -15
  29. package/rules/npm-module/js/skill_meta.mjs +5 -1
  30. package/rules/python/docs/main.md +11 -11
  31. package/rules/rust/docs/main.md +5 -5
  32. package/rules/text/js/cspell-fix.mjs +15 -16
  33. package/rules/text/js/docs/cspell-fix.md +16 -9
  34. package/rules/text/main.mjs +4 -4
  35. package/schemas/skill-meta.json +8 -0
  36. package/scripts/docs/skills-cli.md +21 -25
  37. package/scripts/docs/update-blue-oak.md +8 -8
  38. package/scripts/lib/adr/docs/normalize-cli.md +3 -20
  39. package/scripts/lib/adr/docs/normalize-pipeline.md +3 -33
  40. package/scripts/lib/adr/normalize-cli.mjs +2 -2
  41. package/scripts/lib/adr/normalize-pipeline.mjs +78 -44
  42. package/scripts/lib/docs/discover-checkable-rules.md +6 -6
  43. package/scripts/lib/docs/inline-template-links.md +8 -6
  44. package/scripts/lib/docs/list-project-rules-mdc.md +5 -3
  45. package/scripts/lib/docs/root-notice.md +13 -16
  46. package/scripts/lib/docs/run-lint.md +10 -8
  47. package/scripts/lib/docs/skill-meta.md +29 -10
  48. package/scripts/lib/fix/docs/discover-t0-patterns.md +10 -13
  49. package/scripts/lib/fix/docs/escalation-log.md +10 -9
  50. package/scripts/lib/fix/docs/index.md +0 -1
  51. package/scripts/lib/fix/docs/orchestrator.md +15 -13
  52. package/scripts/lib/fix/escalation-log.mjs +1 -1
  53. package/scripts/lib/fix/orchestrator.mjs +67 -32
  54. package/scripts/lib/run-lint.mjs +2 -10
  55. package/scripts/lib/skill-meta.mjs +22 -0
  56. package/scripts/skills-cli.mjs +52 -14
  57. package/scripts/utils/ast-extract.mjs +105 -0
  58. package/scripts/utils/docs/ast-extract.md +30 -0
  59. package/scripts/utils/docs/walkDir.md +17 -20
  60. package/lib/docs/llm.md +0 -33
  61. package/lib/docs/models.md +0 -48
  62. package/lib/docs/omlx-trace.md +0 -49
  63. package/lib/docs/omlx.md +0 -41
  64. package/lib/llm.mjs +0 -215
  65. package/lib/models.mjs +0 -75
  66. package/lib/omlx-trace.mjs +0 -158
  67. package/lib/omlx.mjs +0 -220
  68. package/scripts/lib/fix/analyze-escalation.mjs +0 -353
  69. package/scripts/lib/fix/docs/analyze-escalation.md +0 -44
  70. package/scripts/lib/fix/docs/llm-fix-apply.md +0 -31
  71. package/scripts/lib/fix/docs/llm-lint-fix.md +0 -31
  72. package/scripts/lib/fix/docs/llm-worker.md +0 -33
  73. package/scripts/lib/fix/docs/verbose-block.md +0 -27
  74. package/scripts/lib/fix/llm-fix-apply.mjs +0 -113
  75. package/scripts/lib/fix/llm-lint-fix.mjs +0 -82
  76. package/scripts/lib/fix/llm-worker.mjs +0 -332
  77. package/scripts/lib/fix/verbose-block.mjs +0 -82
@@ -12,8 +12,24 @@ import {
12
12
  import { basename, dirname, join, relative } from 'node:path'
13
13
 
14
14
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
15
- import { classifyOmlxError, preflightLocalModel } from '../../../lib/llm.mjs'
16
15
  import { generateDoc, DEFAULT_LOCAL_MODEL } from './docgen-gen.mjs'
16
+
17
+ /**
18
+ * Класифікує помилку генерації для batch-логіки (замінює `classifyOmlxError` після
19
+ * pi-міграції — помилки приходять як винятки з generateDoc/pi-one-shot):
20
+ * - `permanent` — pre-send guard «Prompt too long» → skip (не ретраїти);
21
+ * - `systemic` — модель/сервер/registry/RAM упали → circuit-breaker abort;
22
+ * - `transient` — таймаут (можна було б ретраїти);
23
+ * - `infra` — інше (рахуємо як помилку, але без abort).
24
+ * @param {string} msg повідомлення помилки
25
+ * @returns {'permanent'|'systemic'|'transient'|'infra'} клас
26
+ */
27
+ function classifyDocgenError(msg) {
28
+ if (/prompt too long|pre-send guard|too long/i.test(msg)) return 'permanent'
29
+ if (/registry:|session:|не знайдена|memory|enomem|connection refused|econnrefused/i.test(msg)) return 'systemic'
30
+ if (/timeout|etimedout/i.test(msg)) return 'transient'
31
+ return 'infra'
32
+ }
17
33
  import { crc32, stampDoc, readDocQuality, readDocModel, QUALITY_THRESHOLD } from './docgen-crc.mjs'
18
34
  import { resolveRoot, scanForDocFiles, scanOrphanedDocs } from './docgen-scan.mjs'
19
35
 
@@ -134,7 +150,7 @@ async function generateOne(file, root, progress, stats) {
134
150
  }
135
151
  return 'ok'
136
152
  } catch (error) {
137
- const cls = classifyOmlxError(error.message)
153
+ const cls = classifyDocgenError(error.message)
138
154
  if (cls === 'permanent') {
139
155
  stats.skipped.push(file.sourcePath)
140
156
  process.stdout.write(`⊘ skip (permanent): ${error.message}\n`)
@@ -307,9 +323,8 @@ export async function runDocFilesGenCli(argv) {
307
323
  * @returns {Promise<number>} 0 — без помилок; 1 — фейл preflight або є помилки; 2 — systemic-abort
308
324
  */
309
325
  export async function runGenerationBatch(targets, root, { headline } = {}) {
310
- const problem = preflightLocalModel(DEFAULT_LOCAL_MODEL)
311
- if (problem) {
312
- console.error(`✗ fix-doc-files: ${problem}`)
326
+ if (!DEFAULT_LOCAL_MODEL) {
327
+ console.error('✗ fix-doc-files: локальну модель не задано (N_LOCAL_MIN_MODEL)')
313
328
  return 1
314
329
  }
315
330
 
@@ -2,8 +2,8 @@
2
2
  import { readFileSync, existsSync } from 'node:fs'
3
3
  import { basename } from 'node:path'
4
4
  import { env } from 'node:process'
5
- import { resolveModel } from '../../../lib/models.mjs'
6
- import { callLlm as callLlmRaw } from '../../../lib/llm.mjs'
5
+ import { resolveModel } from '../../../lib/pi-model-tiers.mjs'
6
+ import { runOneShot } from '../../../lib/pi-one-shot.mjs'
7
7
  import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
8
8
  import { docPathForSource } from './docgen-scan.mjs'
9
9
  import { extractFacts } from './docgen-extract.mjs'
@@ -23,16 +23,25 @@ import {
23
23
  let llmMeter = { calls: 0, ms: 0 }
24
24
 
25
25
  /**
26
- * Обгортка callLlm з обліком: лічить кількість викликів і сумарний час у них.
27
- * callLlm синхронний (spawnSync/curl), генерація одного файлу послідовна — лічильник без гонок.
28
- * Усі виклики `callLlm(...)` у цьому модулі йдуть через неї автоматично (імпорт як callLlmRaw).
29
- * @param {...any} args ті самі аргументи, що й у callLlm з lib/llm.mjs
30
- * @returns {string} відповідь моделі
26
+ * Обгортка LLM-виклику з обліком (тепер async поверх pi-one-shot): лічить кількість
27
+ * викликів і сумарний час. Генерація одного файлу послідовна — лічильник без гонок.
28
+ * Зберігає старий інтерфейс accountant'а: повертає рядок-вміст, кидає на помилці.
29
+ * @param {Array<{role:string,content:string}>} messages чат-повідомлення
30
+ * @param {string} model model-id (`provider/id`)
31
+ * @param {{ timeoutMs?: number, caller?: string }} [opts] ліміт/мітка (temperature/maxTokens не підтримуються pi-one-shot)
32
+ * @returns {Promise<string>} відповідь моделі
31
33
  */
32
- function callLlm(...args) {
34
+ async function callLlm(messages, model, opts = {}) {
33
35
  const started = Date.now()
34
36
  try {
35
- return callLlmRaw(...args)
37
+ const res = await runOneShot({
38
+ messages,
39
+ modelSpec: model,
40
+ timeoutMs: opts.timeoutMs,
41
+ caller: opts.caller ?? 'docgen'
42
+ })
43
+ if (res.error) throw new Error(res.error)
44
+ return res.content
36
45
  } finally {
37
46
  llmMeter.calls += 1
38
47
  llmMeter.ms += Date.now() - started
@@ -273,10 +282,12 @@ export function scoreDoc(md, facts, { anchors = null, src = '' } = {}) {
273
282
  * @param {number} timeoutMs ліміт на один виклик
274
283
  * @returns {string} фінальний текст секції
275
284
  */
276
- function critiqueRefineSection(sectionKey, draft, facts, anchors, model, timeoutMs) {
277
- const critique = callLlm(criticMessages(sectionKey, draft, facts, anchors), model, { timeoutMs }).trim()
285
+ async function critiqueRefineSection(sectionKey, draft, facts, anchors, model, timeoutMs) {
286
+ const critique = (await callLlm(criticMessages(sectionKey, draft, facts, anchors), model, { timeoutMs })).trim()
278
287
  if (!critique || CRITIC_NONE_RE.test(critique) || critique.length < 12) return draft
279
- const refined = callLlm(refineMessages(sectionKey, draft, critique, facts, anchors), model, { timeoutMs }).trim()
288
+ const refined = (
289
+ await callLlm(refineMessages(sectionKey, draft, critique, facts, anchors), model, { timeoutMs })
290
+ ).trim()
280
291
  return stripSignatures(stripSection(refined)) || draft
281
292
  }
282
293
 
@@ -301,8 +312,8 @@ function apiNeedsRefine(facts) {
301
312
  * @param {{ intent?: string|null }} [opts] захищена секція «Призначення» для збереження
302
313
  * @returns {{ md: string }} зібраний документ
303
314
  */
304
- function oneShotDoc(facts, src, model, timeoutMs = LOCAL_TIMEOUT_MS, { intent = null } = {}) {
305
- const text = callLlm(oneShotMessages(facts, src), model, { timeoutMs })
315
+ async function oneShotDoc(facts, src, model, timeoutMs = LOCAL_TIMEOUT_MS, { intent = null } = {}) {
316
+ const text = await callLlm(oneShotMessages(facts, src), model, { timeoutMs })
306
317
  let md = stripSignatures(stripSection(text))
307
318
  if (!md.startsWith('#')) md = `# ${basename(facts.relPath)}\n\n${md}`
308
319
  return { md: insertProtected(md + '\n', intent) }
@@ -339,27 +350,33 @@ function assemble(stem, sections) {
339
350
  * @param {{ anchors?: object|null, temperature?: number, intent?: string|null }} [opts] анкори, температура, захищена секція як контекст
340
351
  * @returns {{ md: string }} зібраний документ
341
352
  */
342
- function orchestratedDoc(facts, src, model, timeoutMs, { anchors = null, temperature = 0.2, intent = null } = {}) {
353
+ async function orchestratedDoc(
354
+ facts,
355
+ src,
356
+ model,
357
+ timeoutMs,
358
+ { anchors = null, temperature = 0.2, intent = null } = {}
359
+ ) {
343
360
  const sections = {}
344
361
  const anc = anchors ?? extractAnchors(src)
345
362
  // E3: «Гарантії» — детермінований шаблон з markers (0 LLM-запитів, 0 generic-фраз)
346
363
  sections.guarantees = guaranteesFromMarkers(facts)
347
364
  // Спершу Поведінка (+API) — секції з фактажем
348
365
  for (const s of sectionMessages(facts, src, anc, intent)) {
349
- let draft = stripSignatures(stripSection(callLlm(s.messages, model, { timeoutMs, temperature })))
366
+ let draft = stripSignatures(stripSection(await callLlm(s.messages, model, { timeoutMs, temperature })))
350
367
  // E2: critique→refine для API, коли всі описи порожні (модель зриває на generic)
351
368
  if (s.key === 'api' && apiNeedsRefine(facts)) {
352
- draft = critiqueRefineSection(s.key, draft, facts, anc, model, timeoutMs)
369
+ draft = await critiqueRefineSection(s.key, draft, facts, anc, model, timeoutMs)
353
370
  }
354
371
  sections[s.key] = draft
355
372
  }
356
373
  // R3: «Огляд» — ОСТАННІМ, узагальненням уже написаної Поведінки (не голого факт-листа)
357
374
  let overview = stripSignatures(
358
375
  stripSection(
359
- callLlm(overviewMessages(facts, sections.behavior ?? '', anc, intent), model, { timeoutMs, temperature })
376
+ await callLlm(overviewMessages(facts, sections.behavior ?? '', anc, intent), model, { timeoutMs, temperature })
360
377
  )
361
378
  )
362
- overview = critiqueRefineSection('overview', overview, facts, anc, model, timeoutMs)
379
+ overview = await critiqueRefineSection('overview', overview, facts, anc, model, timeoutMs)
363
380
  sections.overview = overview
364
381
  // Варіант B: дослівно повертаємо захищений блок у фіксовану позицію
365
382
  return { md: insertProtected(assemble(basename(facts.relPath), sections), intent) }
@@ -398,7 +415,7 @@ export const DEFAULT_LOCAL_MODEL = env.N_CURSOR_DOCGEN_MODEL ?? resolveModel('mi
398
415
  * @param {{ model?: string, threshold?: number, existingMd?: string|null }} [opts] model-id, поріг degraded, наявна дока (для збереження захищеної секції)
399
416
  * @returns {{ md: string, ms: number, llmMs: number, llmCalls: number, score: number|null, issues: string[], degraded: boolean, model: string }} документ і метадані генерації (ms — увесь файл; llmMs/llmCalls — лише LLM; решта ms — оркестрація)
400
417
  */
401
- export function generateDoc(
418
+ export async function generateDoc(
402
419
  file,
403
420
  { model = DEFAULT_LOCAL_MODEL, threshold = QUALITY_THRESHOLD, existingMd = null } = {}
404
421
  ) {
@@ -421,8 +438,8 @@ export function generateDoc(
421
438
  const intent = existingMd ? splitProtected(existingMd).body : null
422
439
  const anchors = facts.unsupported ? null : extractAnchors(src)
423
440
  let r = facts.unsupported
424
- ? oneShotDoc(facts, src, model, LOCAL_TIMEOUT_MS, { intent })
425
- : orchestratedDoc(facts, src, model, LOCAL_TIMEOUT_MS, { anchors, intent })
441
+ ? await oneShotDoc(facts, src, model, LOCAL_TIMEOUT_MS, { intent })
442
+ : await orchestratedDoc(facts, src, model, LOCAL_TIMEOUT_MS, { anchors, intent })
426
443
 
427
444
  // unsupported (vue/py до юніт-шару): скорер не застосовний — score=null, не degraded
428
445
  if (facts.unsupported) {
@@ -444,7 +461,7 @@ export function generateDoc(
444
461
  // E4: best-of-2 — один retry з вищою температурою, det-вибір кращого
445
462
  if (score < threshold && env.N_CURSOR_DOCGEN_BEST_OF !== '0') {
446
463
  try {
447
- const r2 = orchestratedDoc(facts, src, model, LOCAL_TIMEOUT_MS, { anchors, temperature: 0.5, intent })
464
+ const r2 = await orchestratedDoc(facts, src, model, LOCAL_TIMEOUT_MS, { anchors, temperature: 0.5, intent })
448
465
  const s2 = scoreDoc(r2.md, facts, { anchors, src })
449
466
  if (s2.score > score) {
450
467
  r = r2
@@ -463,7 +480,7 @@ export function generateDoc(
463
480
  let judge = null
464
481
  if (JUDGE_ENABLED && score >= threshold) {
465
482
  try {
466
- judge = { ...judgeDoc(src, r.md), model: JUDGE_MODEL }
483
+ judge = { ...(await judgeDoc(src, r.md)), model: JUDGE_MODEL }
467
484
  if (judgeFailsDoc(judge)) issues = [...issues, `judge:inaccurate:${judge.confidence}`]
468
485
  } catch (error) {
469
486
  issues = [...issues, `judge:error: ${error.message.slice(0, 80)}`]
@@ -495,7 +512,7 @@ if (isRunAsCli(import.meta.url)) {
495
512
  // Зберегти захищену секцію «Призначення», якщо дока вже існує
496
513
  const docPath = docPathForSource(file)
497
514
  const existingMd = existsSync(docPath) ? readFileSync(docPath, 'utf8') : null
498
- const r = generateDoc(file, { model, existingMd })
515
+ const r = await generateDoc(file, { model, existingMd })
499
516
  const issuesTxt = r.issues?.length ? ` issues=${r.issues.join(',')}` : ''
500
517
  process.stderr.write(
501
518
  `[local ${r.model}] ${r.ms}ms (llm ${r.llmMs}ms/${r.llmCalls} calls, orch ${r.ms - r.llmMs}ms) / score=${r.score}${r.degraded ? ' DEGRADED' : ''}${issuesTxt}\n`
@@ -3,7 +3,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
3
3
  import { createHash } from 'node:crypto'
4
4
  import { join } from 'node:path'
5
5
  import { generateDoc } from './docgen-gen.mjs'
6
- import { callLlm } from '../../../lib/llm.mjs'
6
+ import { runOneShot } from '../../../lib/pi-one-shot.mjs'
7
7
  import { QUALITY_THRESHOLD } from './docgen-crc.mjs'
8
8
 
9
9
  const env = process.env
@@ -43,11 +43,11 @@ function cacheSet(key, val) {
43
43
  * @param {string} src вміст файлу
44
44
  * @returns {{md: string, score: number|null, issues: string[], degraded: boolean, cached: boolean}} результат генерації
45
45
  */
46
- function genCached(file, src) {
46
+ async function genCached(file, src) {
47
47
  const key = 'gen-' + sha(GEN_MODEL + '\0' + src)
48
48
  const hit = cacheGet(key)
49
49
  if (hit) return { ...hit, cached: true }
50
- const r = generateDoc(file, { model: GEN_MODEL })
50
+ const r = await generateDoc(file, { model: GEN_MODEL })
51
51
  const out = { md: r.md, score: r.score, issues: r.issues, degraded: r.degraded }
52
52
  cacheSet(key, out)
53
53
  return { ...out, cached: false }
@@ -59,19 +59,22 @@ function genCached(file, src) {
59
59
  * @param {string} doc згенерована документація
60
60
  * @returns {{verdict: string, confidence: number, reason: string, offending?: string[], cached: boolean}} verdict судді
61
61
  */
62
- function judgeCached(src, doc) {
62
+ async function judgeCached(src, doc) {
63
63
  const key = 'judge-' + sha(JUDGE_MODEL + '\0' + src + '\0' + doc)
64
64
  const hit = cacheGet(key)
65
65
  if (hit) return { ...hit, cached: true }
66
66
  const user = `SOURCE FILE:\n\`\`\`\n${src.slice(0, 12000)}\n\`\`\`\n\nGENERATED DOC:\n\`\`\`md\n${doc.slice(0, 8000)}\n\`\`\`\n\nReturn the JSON verdict.`
67
- const raw = callLlm(
68
- [
67
+ const res = await runOneShot({
68
+ messages: [
69
69
  { role: 'system', content: SYSTEM },
70
70
  { role: 'user', content: user }
71
71
  ],
72
- JUDGE_MODEL,
73
- { timeoutMs: JUDGE_TIMEOUT, temperature: 0 }
74
- )
72
+ modelSpec: JUDGE_MODEL,
73
+ timeoutMs: JUDGE_TIMEOUT,
74
+ caller: 'docgen-measure'
75
+ })
76
+ if (res.error) throw new Error(res.error)
77
+ const raw = res.content
75
78
  const a = raw.indexOf('{'),
76
79
  b = raw.lastIndexOf('}')
77
80
  if (a === -1 || b === -1) throw new Error('no JSON in judge reply: ' + raw.slice(0, 160))
@@ -83,7 +86,7 @@ function judgeCached(src, doc) {
83
86
  /**
84
87
  *
85
88
  */
86
- function main() {
89
+ async function main() {
87
90
  const files = process.argv.slice(2).filter(f => !f.startsWith('--'))
88
91
  if (!files.length) {
89
92
  console.error('Usage: node docgen-judge-measure.mjs <file1> <file2> ...')
@@ -106,7 +109,7 @@ function main() {
106
109
 
107
110
  let gen
108
111
  try {
109
- gen = genCached(file, src)
112
+ gen = await genCached(file, src)
110
113
  } catch (error) {
111
114
  console.error(`[gen-err] ${tag}: ${error.message.slice(0, 120)}`)
112
115
  rows.push({ file, error: 'gen', detail: error.message.slice(0, 200) })
@@ -124,7 +127,7 @@ function main() {
124
127
 
125
128
  if (passed) {
126
129
  try {
127
- const v = judgeCached(src, gen.md)
130
+ const v = await judgeCached(src, gen.md)
128
131
  row.verdict = v.verdict
129
132
  row.confidence = v.confidence
130
133
  row.reason = v.reason
@@ -195,4 +198,4 @@ function main() {
195
198
  console.log(`report: ${out}`)
196
199
  }
197
200
 
198
- main()
201
+ await main()
@@ -12,8 +12,8 @@
12
12
  * `N_CLOUD_MIN_MODEL` → 0 змін поведінки. Патерн дзеркалить `scripts/coverage-classify`.
13
13
  */
14
14
  import { env } from 'node:process'
15
- import { callLlm } from '../../../lib/llm.mjs'
16
- import { CLOUD_MIN } from '../../../lib/models.mjs'
15
+ import { runOneShot } from '../../../lib/pi-one-shot.mjs'
16
+ import { CLOUD_MIN } from '../../../lib/pi-model-tiers.mjs'
17
17
 
18
18
  /** Модель-суддя = `N_CLOUD_MIN_MODEL` (хмарний cloud-min tier). */
19
19
  export const JUDGE_MODEL = CLOUD_MIN
@@ -56,17 +56,19 @@ export function parseDocVerdict(rawText) {
56
56
  * @param {{model?: string, timeoutMs?: number}} [opts] override моделі/таймауту
57
57
  * @returns {{verdict: string, confidence: number, reason: string}} verdict судді
58
58
  */
59
- export function judgeDoc(src, doc, { model = JUDGE_MODEL, timeoutMs = 120_000 } = {}) {
59
+ export async function judgeDoc(src, doc, { model = JUDGE_MODEL, timeoutMs = 120_000 } = {}) {
60
60
  const user = `SOURCE FILE:\n\`\`\`\n${src.slice(0, 12_000)}\n\`\`\`\n\nGENERATED DOC:\n\`\`\`md\n${doc.slice(0, 8000)}\n\`\`\`\n\nReturn the JSON verdict.`
61
- const raw = callLlm(
62
- [
61
+ const res = await runOneShot({
62
+ messages: [
63
63
  { role: 'system', content: JUDGE_SYSTEM },
64
64
  { role: 'user', content: user }
65
65
  ],
66
- model,
67
- { timeoutMs, temperature: 0 }
68
- )
69
- return parseDocVerdict(raw)
66
+ modelSpec: model,
67
+ timeoutMs,
68
+ caller: 'docgen-judge'
69
+ })
70
+ if (res.error) throw new Error(res.error)
71
+ return parseDocVerdict(res.content)
70
72
  }
71
73
 
72
74
  /**
@@ -3,29 +3,12 @@ type: JS Module
3
3
  title: docgen-files-batch.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-files-batch.mjs
5
5
  docgen:
6
- crc: 6316eecb
6
+ crc: 26217123
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 55
9
+ issues: no-overview,short-behavior,best-of-2:retry-lost
9
10
  ---
10
11
 
11
- Модуль керує життєвим циклом документації. Він вибирає цілі для оновлення за допомогою `selectTargets`, очищає від неіснуючих джерел за допомогою `purgeOrphanedDocs`, запускає генерацію файлів через `runDocFilesGenCli` та `runDocFilesStampCli`, а також виконує пакетну генерацію за допомогою `runGenerationBatch`. Усі операції виконуються з механізмом перехоплення помилок, що запобігає виникненню винятків назовні.
12
-
13
- ## Поведінка
14
-
15
- selectTargets відфільтровує документи для генерації, вибираючи застарілі або ті, які мають низьку якість і не були спробовані раніше.
16
- purgeOrphanedDocs видаляє документи, для яких не існує відповідного джерела, і оновлює індекси директорій.
17
- runDocFilesGenCli запускає генерацію документації для застарілих або низькоякісних файлів, після попереднього очищення сирітських документів.
18
- runGenerationBatch виконує послідовну генерацію документації для заданого набору цілей, керуючи процесом через механізм виходу з ладу.
19
- runDocFilesStampCli детерміновано оновлює метадані (frontmatter) існуючих документів, додаючи CRC та зберігаючи дані про якість.
20
-
21
- ## Публічний API
22
-
23
- selectTargets — Визначає цілі для генерації документації: застарілі або деградовані документи, або всі документи при використанні прапора перезапису.
24
- purgeOrphanedDocs — Видаляє документи, для яких відсутній відповідний вихідний файл, та оновлює файл індексу.
25
- runDocFilesGenCli — Генерує документацію для застарілих або відсутніх документів.
26
- runGenerationBatch — Виконує повний цикл генерації: перевіряє стан локального бекенду, послідовно генерує документи з обробкою збоїв, та створює фінальний звіт.
27
- runDocFilesStampCli — Додає або оновлює метадані джерела та контрольної суми до існуючих документів без використання великих мовних моделей.
28
-
29
12
  ## Гарантії поведінки
30
13
 
31
14
  - Перехоплює помилки і не пропускає винятків назовні (fail-safe).
@@ -3,29 +3,12 @@ type: JS Module
3
3
  title: docgen-gen.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-gen.mjs
5
5
  docgen:
6
- crc: 059d7d6e
6
+ crc: 2d3245b9
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 50
9
+ issues: no-overview,short-behavior,anchor-miss:(abie.mdc),best-of-2:retry-lost
9
10
  ---
10
11
 
11
- Модуль забезпечує повний цикл роботи з документами: відокремлює захищений блок за допомогою `splitProtected`, вставляє його у визначене місце за допомогою `insertProtected`, генерує повний Markdown-документ через `generateDoc` (з використанням `DEFAULT_LOCAL_MODEL`), а також оцінює якість документа за допомогою `scoreDoc`. Кешування застосовується протягом прогону. (abie.mdc)
12
-
13
- ## Поведінка
14
-
15
- splitProtected відокремлює захищений блок Призначення від основного документа.
16
- insertProtected вставляє захищений блок Призначення у фіксовану позицію після заголовка документа.
17
- scoreDoc оцінює якість згенерованого документа за заданими критеріями.
18
- DEFAULT_LOCAL_MODEL визначає модель, яка використовується для генерації документа.
19
- generateDoc генерує повний Markdown-документ з опису файлу, застосовуючи оцінку та можливий ретрай.
20
-
21
- ## Публічний API
22
-
23
- splitProtected — Розділяє захищений розділ `## Призначення` (Варіант B), використовуючи наступний заголовок рівня H2 або H3 як межу.
24
- insertProtected — Розміщує захищений блок `## Призначення` безпосередньо після основного заголовка (H1).
25
- scoreDoc — Виконує детерміновану оцінку (Stage 2.5), порівнюючи вихідні дані з фактичною інформацією.
26
- DEFAULT_LOCAL_MODEL — Визначає модель для генерації документа, використовуючи мінімальну локальну модель. Якщо модель не задана, процес зупиняється з помилкою.
27
- generateDoc — Основна функція, яка перетворює вхідний файл у Markdown-документ з оцінкою достовірності.
28
-
29
12
  ## Гарантії поведінки
30
13
 
31
14
  - Кешує результати в межах одного прогону.
@@ -3,27 +3,12 @@ type: JS Module
3
3
  title: docgen-judge-measure.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-judge-measure.mjs
5
5
  docgen:
6
- crc: 86b3120f
6
+ crc: 4362c310
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 50
9
+ issues: no-overview,short-behavior,anchor-miss:report.json,best-of-2:retry-lost
9
10
  ---
10
11
 
11
- Файл аналізує надані файли коду, створюючи документацію та оцінюючи її якість відповідно до конфігурації, що міститься у report.json. Процес збирає результати для кожного файлу, використовуючи кешування у межах прогону на основі вмісту джерела та вже згенерованої документації. У кінці процес агрегує дані та зберігає повний звіт у report.json, а також виводить його у консоль.
12
-
13
- ## Поведінка
14
-
15
- 1. Зчитує список файлів для аналізу з аргументів командного рядка.
16
- 2. Для кожного файлу зчитує його вміст.
17
- 3. Генерує документацію для файлу, використовуючи кешування за вмістом джерела.
18
- 4. Якщо генерація документації успішна і отриманий бал перевищує встановлений поріг, переходить до етапу оцінки.
19
- 5. Якщо бал не перевищує порогу, файл вважається "degraded" (зниженою якістю) і не підлягає подальшій оцінці.
20
- 6. Якщо бал перевищує поріг, документація передається для оцінки потужній моделі, використовуючи кешування за вмістом джерела та згенерованою документацією.
21
- 7. Оцінка повертає вердикт ("accurate", "generic" або "inaccurate"), впевненість та причину.
22
- 8. Збираються результати для кожного файлу.
23
- 9. Після обробки всіх файлів агрегуються результати: підраховуються загальні показники (кількість файлів, помилки генерації, кількість пройшлих тестів, розподіл вердиктів).
24
- 10. Зберігається звіт у файл `report.json` у каталозі кешу.
25
- 11. Виводиться консольний звіт про результати вимірювання.
26
-
27
12
  ## Гарантії поведінки
28
13
 
29
14
  - Кешує результати в межах одного прогону.
@@ -3,31 +3,12 @@ type: JS Module
3
3
  title: docgen-judge.mjs
4
4
  resource: npm/rules/doc-files/js/docgen-judge.mjs
5
5
  docgen:
6
- crc: fcbf72fa
6
+ crc: d20a0bc7
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 55
9
+ issues: no-overview,short-behavior,best-of-2:retry-lost
9
10
  ---
10
11
 
11
- Модуль реалізує механізм оцінки якості документації. Він визначає модель для оцінки за допомогою `JUDGE_MODEL`, перевіряє статус активності гейту через `JUDGE_ENABLED` та встановлює поріг впевненості за допомогою `JUDGE_CONFIDENCE`. Модуль отримує, парсить та визначає фінальний статус документації, викликаючи `judgeDoc` для отримання висновків, а також може використовувати `judgeFailsDoc` для визначення провалу.
12
-
13
- ## Поведінка
14
-
15
- JUDGE_MODEL — Вказує модель, яку використовує суддя для оцінки документації.
16
- JUDGE_ENABLED — Позначає, чи активний семантичний гейт судді.
17
- JUDGE_CONFIDENCE — Визначає мінімальну впевненість, необхідну для позначення документації як деградованої.
18
- parseDocVerdict — Витягує та валідує об'єкт оцінки з сирого текстового виводу судді.
19
- judgeDoc — Зіставляє вміст вихідного файлу та згенеровану документацію, щоб отримати оцінку від судді.
20
- judgeFailsDoc — Визначає, чи слід вважати документацію деградованою на основі оцінки судді.
21
-
22
- ## Публічний API
23
-
24
- JUDGE_MODEL — Визначає модель-суддю як `N_CLOUD_MIN_MODEL` (хмарний мінімальний рівень).
25
- JUDGE_ENABLED — Автоматично вмикає механізм суддівства, якщо обрано `N_CLOUD_MIN_MODEL`.
26
- JUDGE_CONFIDENCE — Встановлює мінімальний рівень впевненості для позначення документа як погіршеного (degraded) через неточність.
27
- parseDocVerdict — Витягує та перевіряє JSON-вердикт із відповіді великої мовної моделі.
28
- judgeDoc — Оцінює згенерований документ потужною моделлю порівняно з оригінальним джерелом.
29
- judgeFailsDoc — Визначає, чи повинен документ бути позначений як погіршений, якщо вердикт є `inaccurate` і впевненість достатня.
30
-
31
12
  ## Гарантії поведінки
32
13
 
33
14
  - Read-only: не виконує операцій запису (ФС/БД).
@@ -3,31 +3,38 @@ type: JS Module
3
3
  title: skill_meta.mjs
4
4
  resource: npm/rules/npm-module/js/skill_meta.mjs
5
5
  docgen:
6
- crc: d02dd00d
6
+ crc: a134317c
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
+ issues: judge:inaccurate:0.98
10
+ judgeModel: openai-codex/gpt-5.4-mini
9
11
  ---
10
12
 
11
- Перевіряє структуру та конфігурацію скілів у каталозі `npm/skills`, використовуючи правила, визначені в `meta.json`. Валідує, що кожен скіл не містить файлу `auto.md`. Перевіряє валідність полів `worktree`, `auto` та `requireRoot` у `main.json` кожного скіла.
13
+ ## Огляд
14
+
15
+ Скрипт проводить валідацію конфігурацій усіх каталогів у `npm/skills`, перевіряючи відповідність кожного скіла встановленому контракту. Аналіз здійснюється з урахуванням конфігурацій `meta.json` та `main.json`, що контролюють надійність полів, таких як `worktree`, `auto` та `requireRoot`. У разі успіху повідомляється про відповідність конфігурацій скілів визначеним правилам; у випадку виявлення помилок інформується про них.
12
16
 
13
17
  ## Поведінка
14
18
 
15
- 1. Перевіряє наявність каталогу `npm/skills` у поточному робочому каталозі. Якщо каталог відсутній, операція завершується успішно.
16
- 2. Ітерує по всіх підкаталогах у `npm/skills`.
17
- 3. Для кожного підкаталогу виконується перевірка метаданих скіла:
18
- а. Перевіряється наявність файлу `auto.md` у каталозі скіла. Якщо він присутній, це вважається порушенням.
19
- б. Зчитується вміст `main.json` скіла. Якщо файл відсутній або невалідний, перевірка для цього скіла припиняється.
20
- в. Виконується перевірка полів `main.json` скіла:
21
- i. `worktree` має бути булевим значенням.
22
- ii. Якщо поле `auto` визначене, його вміст має бути розпізнаним як "завжди" або непорожній масив правил.
23
- iii. `requireRoot` має бути булевим значенням, якщо визначений.
24
- iv. Якщо `worktree` встановлено як `true`, а `requireRoot` як `false`, це вважається порушенням.
25
- г. Якщо всі поля `main.json` валідні, це вважається успішним результатом для скіла.
26
- 4. Після перевірки всіх скілів повертається код виходу, що відображає загальний статус (0 успіх, 1 порушення).
19
+ Поведінка
20
+
21
+ 1. Викликає check для перевірки всіх каталогів у `npm/skills`.
22
+ 2. Якщо каталог `npm/skills` відсутній, повідомляє про це як успішний результат, оскільки скілів для валідації немає.
23
+ 3. Для кожного каталогу скіла виконує валідацію.
24
+ 4. Перевіряє, чи існує файл `auto.md` у каталозі скіла; якщо так, повідомляє про його залишковий статус, оскільки метадані тепер знаходяться у `main.json`.
25
+ 5. Зчитує вміст `main.json` скіла. Якщо файл відсутній або невалідний, повідомляє про це і припиняє перевірку для цього скіла.
26
+ 6. Якщо `main.json` знайдено, викликає внутрішню перевірку полів для валідації його структури.
27
+ 7. Перевіряє, чи `worktree` є булевим значенням. Якщо ні, повідомляє про помилку.
28
+ 8. Перевіряє поле `auto`; якщо воно визначене, викликає внутрішній аналіз для визначення, чи є його значення коректним ("завжди" або непорожній масив правил). Якщо ні, повідомляє про нерозпізнаність.
29
+ 9. Перевіряє поле `requireRoot`; якщо воно визначене, перевіряє, чи є воно булевим значенням. Якщо ні, повідомляє про помилку.
30
+ 10. Перевіряє на суперечливість: якщо `worktree` встановлено як `true`, а `requireRoot` як `false`, повідомляє про конфігураційну помилку, оскільки `worktree` вже вимагає кореня.
31
+ 11. Перевіряє поле `tier`; якщо воно визначене, перевіряє, чи є воно рядком і чи належить до визначених категорій скілів. Якщо ні, повідомляє про допустимі значення.
32
+ 12. Якщо всі поля `main.json` валідні, повідомляє про успішну валідацію скіла.
33
+ 13. Повертає загальний код завершення, що відображає всі зафіксовані порушення в каталогах скілів.
27
34
 
28
35
  ## Публічний API
29
36
 
30
- check — перевіряє відповідність усіх файлів `meta.json` у каталозі `npm/skills/<id>/` заданим критеріям.
37
+ check — перевіряє відповідність структури всіх файлів конфігурації `npm/skills/<id>/meta.json` вимогам.
31
38
 
32
39
  ## Гарантії поведінки
33
40
 
@@ -3,7 +3,7 @@ import { existsSync, readdirSync } from 'node:fs'
3
3
  import { join } from 'node:path'
4
4
 
5
5
  import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
6
- import { parseSkillAutoSpec, readSkillMetaRaw } from '../../../scripts/lib/skill-meta.mjs'
6
+ import { SKILL_TIERS, parseSkillAutoSpec, readSkillMetaRaw } from '../../../scripts/lib/skill-meta.mjs'
7
7
 
8
8
  /**
9
9
  * Перевіряє поля сирого meta.json одного скіла (без auto.md / відсутності файлу).
@@ -32,6 +32,10 @@ function checkSkillFields(id, raw, reporter) {
32
32
  )
33
33
  ok = false
34
34
  }
35
+ if (raw.tier !== undefined && !(typeof raw.tier === 'string' && SKILL_TIERS.includes(raw.tier))) {
36
+ reporter.fail(`skills/${id}: main.json.tier має бути ${SKILL_TIERS.map(t => `"${t}"`).join(' | ')}`)
37
+ ok = false
38
+ }
35
39
  return ok
36
40
  }
37
41
 
@@ -3,28 +3,28 @@ type: JS Module
3
3
  title: main.mjs
4
4
  resource: npm/rules/python/main.mjs
5
5
  docgen:
6
- crc: b072766d
6
+ crc: 2bdec93d
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 90
8
+ score: 95
9
9
  ---
10
10
 
11
11
  ## Огляд
12
12
 
13
- Виконує послідовність кроків для валідації коду Python відповідно до правил, визначених у (python.mdc). Перед запуском інструментів перевіряє наявність `pyproject.toml` у корені; якщо він відсутній, завершує роботу з кодом 0. Якщо файл присутній, перевіряє наявність `uv` у PATH, оскільки він є єдиним пакет-менеджером. Обов'язково виконує `uv lock --check` для підтвердження актуальності lock-файлу та `uv sync --frozen` для синхронізації середовища з `uv.lock` (див. https://docs.astral.sh/uv/). Опційні лінтери запускаються лише за умови їх доступності через `uv run`. При використанні `ruff` застосовується автоматичне виправлення (`auto-fix`), що модифікує робоче дерево. Усі операції виконуються з механізмом перехоплення помилок (fail-safe), запобігаючи викиданню винятків назовні.
13
+ Цей модуль виконує послідовність кроків для забезпечення якості коду Python, використовуючи інструменти з екосистеми [uv](https://docs.astral.sh/uv/). Якщо файл `pyproject.toml` відсутній у корені, процес завершується з кодом 0 без запуску інструментів. Якщо файл присутній, але `uv` не знайдено в PATH, це розглядається як помилка. Модуль гарантує актуальність lock-файлу (`uv lock --check`) та збірку середовища (`uv sync --frozen`) перед запуском лінтерів. Опціональні лінтери запускаються лише за умови їх доступності через `uv run`.
14
14
 
15
15
  ## Поведінка
16
16
 
17
- run виконує стандартну перевірку на основі контексту.
18
- runLintPythonSteps виконує повний цикл лінтування Python, включаючи перевірку `uv lock --check`, `uv sync --frozen` та запуск лінтерів, якщо `pyproject.toml` присутній.
19
- runLintPython запускає кроки лінтування Python, використовуючи механізм серіалізації через `runStandardLint`.
20
- lint оркеструє запуск `runLintPython` з опцією `readOnly` для детектних перевірок.
17
+ run виконує стандартну перевірку проєкту, використовуючи контекст прогону.
18
+ runLintPythonSteps виконує повний набір кроків лінтування Python, включаючи перевірку `pyproject.toml`, виконання `uv lock --check` та `uv sync --frozen`, а також запуск опціональних лінтерів (`ruff`, `mypy`) та перевірку ліцензій.
19
+ runLintPython запускає повний процес лінтування Python, серіалізуючи його через стандартний механізм перевірки.
20
+ lint оркеструє запуск `runLintPython`, надаючи можливість вказати режим без мутацій (`readOnly`).
21
21
 
22
22
  ## Публічний API
23
23
 
24
- run — Точка входу для виконання правил, яка перевіряє аспекти застосування (JS-занепокложення $\rightarrow$ політика $\rightarrow$ mdc-посилання).
25
- runLintPythonSteps — Виконує внутрішні етапи перевірки коду Python без збереження результатів.
26
- runLintPython — Публічний інтерфейс командного рядка для запуску перевірки коду Python, що забезпечує унікальність завдяки блоку блокування та аналізу стану Git-дерева.
27
- lint — Адаптер, що керує запуском перевірки коду Python, делегуючи це `runLintPython`.
24
+ run — Основна точка входу правила, яка виконує перевірку (JS-занепокложення $\rightarrow$ політика $\rightarrow$ mdc-посилання) та викликає перевірку якості коду.
25
+ runLintPythonSteps — Виконує внутрішні етапи перевірки якості Python-коду без збереження логів.
26
+ runLintPython — Публічний інтерфейс для запуску перевірки якості Python-коду, який синхронізується з станом Git-дерева.
27
+ lint — Координатор, що ініціює запуск перевірки якості Python-коду, делегуючи цю роботу `runLintPython`.
28
28
 
29
29
  ## Гарантії поведінки
30
30