@nitra/cursor 12.15.1 → 12.16.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/n-cursor.js +1 -1
  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/doc-files/js/docgen-files-batch.mjs +20 -5
  20. package/rules/doc-files/js/docgen-gen.mjs +42 -25
  21. package/rules/doc-files/js/docgen-judge-measure.mjs +16 -13
  22. package/rules/doc-files/js/docgen-judge.mjs +11 -9
  23. package/rules/doc-files/js/docs/docgen-files-batch.md +3 -20
  24. package/rules/doc-files/js/docs/docgen-gen.md +3 -20
  25. package/rules/doc-files/js/docs/docgen-judge-measure.md +3 -18
  26. package/rules/doc-files/js/docs/docgen-judge.md +3 -22
  27. package/rules/npm-module/js/docs/skill_meta.md +22 -15
  28. package/rules/npm-module/js/skill_meta.mjs +5 -1
  29. package/rules/text/js/cspell-fix.mjs +15 -16
  30. package/rules/text/js/docs/cspell-fix.md +16 -9
  31. package/rules/text/main.mjs +4 -4
  32. package/schemas/skill-meta.json +8 -0
  33. package/scripts/docs/skills-cli.md +21 -25
  34. package/scripts/lib/adr/docs/normalize-cli.md +3 -20
  35. package/scripts/lib/adr/docs/normalize-pipeline.md +3 -33
  36. package/scripts/lib/adr/normalize-cli.mjs +2 -2
  37. package/scripts/lib/adr/normalize-pipeline.mjs +78 -44
  38. package/scripts/lib/docs/skill-meta.md +27 -10
  39. package/scripts/lib/fix/docs/escalation-log.md +10 -9
  40. package/scripts/lib/fix/docs/orchestrator.md +13 -20
  41. package/scripts/lib/fix/escalation-log.mjs +1 -1
  42. package/scripts/lib/fix/orchestrator.mjs +65 -31
  43. package/scripts/lib/skill-meta.mjs +22 -0
  44. package/scripts/skills-cli.mjs +52 -14
  45. package/scripts/utils/ast-extract.mjs +105 -0
  46. package/scripts/utils/docs/ast-extract.md +30 -0
  47. package/lib/docs/llm.md +0 -33
  48. package/lib/docs/models.md +0 -48
  49. package/lib/docs/omlx-trace.md +0 -49
  50. package/lib/docs/omlx.md +0 -41
  51. package/lib/llm.mjs +0 -215
  52. package/lib/models.mjs +0 -75
  53. package/lib/omlx-trace.mjs +0 -158
  54. package/lib/omlx.mjs +0 -220
  55. package/scripts/lib/fix/docs/llm-fix-apply.md +0 -31
  56. package/scripts/lib/fix/docs/llm-lint-fix.md +0 -31
  57. package/scripts/lib/fix/docs/llm-worker.md +0 -28
  58. package/scripts/lib/fix/docs/verbose-block.md +0 -27
  59. package/scripts/lib/fix/llm-fix-apply.mjs +0 -113
  60. package/scripts/lib/fix/llm-lint-fix.mjs +0 -82
  61. package/scripts/lib/fix/llm-worker.mjs +0 -346
  62. package/scripts/lib/fix/verbose-block.mjs +0 -82
@@ -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
 
@@ -20,7 +20,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs'
20
20
  import { join } from 'node:path'
21
21
 
22
22
  import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
23
- import { callLlm, preflightLocalModel } from '../../../lib/llm.mjs'
23
+ import { runOneShot } from '../../../lib/pi-one-shot.mjs'
24
24
 
25
25
  /** Слово у рядку cspell: `<file>:<line>:<col> - Unknown word (xxx)`. */
26
26
  const UNKNOWN_WORD_RE = /Unknown word \(([^)]+)\)/u
@@ -114,10 +114,10 @@ export function appendWordsToDict(cwd, words) {
114
114
  * cspell-крок lint-text: класифікація → словник (нова схема).
115
115
  * @param {string} [cwd] корінь
116
116
  * @param {boolean} [readOnly] true → лише детект (нуль мутацій)
117
- * @param {boolean} [llmFix] opt-in omlx-класифікація (з `meta.json: llmFix:true`); без нього — лише детект
118
- * @returns {number} 0 — чисто; 1 — лишились знахідки / помилка середовища
117
+ * @param {boolean} [llmFix] opt-in LLM-класифікація (з `meta.json: llmFix:true`); без нього — лише детект
118
+ * @returns {Promise<number>} 0 — чисто; 1 — лишились знахідки / помилка середовища
119
119
  */
120
- export function runCspellText(cwd = process.cwd(), readOnly = false, llmFix = false) {
120
+ export async function runCspellText(cwd = process.cwd(), readOnly = false, llmFix = false) {
121
121
  const bin = resolveCmd('npx')
122
122
  if (!bin) {
123
123
  process.stderr.write('❌ npx не знайдено в PATH (cspell).\n')
@@ -133,9 +133,8 @@ export function runCspellText(cwd = process.cwd(), readOnly = false, llmFix = fa
133
133
 
134
134
  // Fix-режим: класифікація знахідок (bounded JSON-вихід), валідні → у словник.
135
135
  const model = fixModel()
136
- const problem = preflightLocalModel(model)
137
- if (problem) {
138
- process.stdout.write(`⚠️ cspell: класифікацію пропущено (${problem})\n`)
136
+ if (!model) {
137
+ process.stdout.write('⚠️ cspell: класифікацію пропущено (локальну модель не задано)\n')
139
138
  process.stdout.write(first.out)
140
139
  return first.code
141
140
  }
@@ -148,19 +147,19 @@ export function runCspellText(cwd = process.cwd(), readOnly = false, llmFix = fa
148
147
  )
149
148
  }
150
149
 
151
- let text
152
- try {
153
- text = callLlm([{ role: 'user', content: classifyPrompt(batch) }], model, {
154
- caller: 'cspell-classify',
155
- maxTokens: 4000
156
- })
157
- } catch (error) {
158
- process.stdout.write(`⚠️ cspell: omlx-класифікація впала (${error.message}) — без авто-словника\n`)
150
+ const res = await runOneShot({
151
+ messages: [{ role: 'user', content: classifyPrompt(batch) }],
152
+ modelSpec: model,
153
+ caller: 'cspell-classify',
154
+ cwd
155
+ })
156
+ if (res.error) {
157
+ process.stdout.write(`⚠️ cspell: LLM-класифікація впала (${res.error}) — без авто-словника\n`)
159
158
  process.stdout.write(first.out)
160
159
  return first.code
161
160
  }
162
161
 
163
- const parsed = parseClassify(text)
162
+ const parsed = parseClassify(res.content)
164
163
  if (!parsed) {
165
164
  process.stdout.write('⚠️ cspell: не вдалося розпарсити класифікацію — без авто-словника\n')
166
165
  process.stdout.write(first.out)
@@ -3,24 +3,31 @@ type: JS Module
3
3
  title: cspell-fix.mjs
4
4
  resource: npm/rules/text/js/cspell-fix.mjs
5
5
  docgen:
6
- crc: e00c233e
6
+ crc: 3469ac34
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
- score: 100
8
+ score: 95
9
9
  ---
10
10
 
11
- Модуль інтегрує cspell у ланцюжку lint-text для класифікації невідомих слів згідно зі схемою docs/specs/2026-06-15-opportunistic-llm-fix-tier.md. Він виявляє невідомі слова, класифікує їх за допомогою LLM у межах omlx-класифікації. Валідні слова автоматично дописуються до `.cspell.json#words` (відповідно до конфігурацій .cspell.json та meta.json). Невалідні знахідки залишаються для ручного рев'ю. Процес є read-only (нуль мутацій), оскільки fix-режим не переписує файли, а лише класифікує знахідки. Після дописування словника виконується re-detect. Гейт-механізм гарантує, що cspell поверне ненульовий код, якщо нерозкласифіковані або потенційні одруки залишилися.
11
+ ## Огляд
12
+
13
+ Огляд: Цей модуль інтегрує `cspell` у ланцюжок `lint-text` для забезпечення термінологічної коректності у тексті, використовуючи схему `omlx-класифікації`. Він ідентифікує неозначені слова, класифікує їх за допомогою LLM та автоматично доповнює словник `.cspell.json` валідними термінами, залишаючи потенційні помилки для ручного рев'ю.
14
+
15
+ Поведінка:
16
+ unknownWords витягує унікальні неозначені слова з виводу інструмента `cspell`.
17
+ appendWordsToDict додає визначені як валідні слова до конфігураційного файлу `.cspell.json`, оновлюючи його в алфавітному порядку.
18
+ runCspellText запускає процес класифікації неозначених слів, спочатку визначаючи їх, а потім, за умови увімкненого LLM-класифікатора, відправляючи їх на перевірку та додаючи валідні слова до словника, після чого повторно перевіряє код.
12
19
 
13
20
  ## Поведінка
14
21
 
15
- unknownWords витягує унікальні «Unknown word» з виводу cspell.
16
- appendWordsToDict дописує класифіковані валідні слова у `.cspell.json#words`, сортуючи та усуваючи дублікати.
17
- runCspellText запускає cspell, класифікує знахідки за допомогою LLM (якщо `llmFix` встановлено), дописує валідні слова у словник та повторно запускає cspell для перевірки, повертаючи код виходу.
22
+ unknownWords витягує унікальні неозначені слова з виводу інструмента `cspell`.
23
+ appendWordsToDict додає визначені як валідні слова до конфігураційного файлу `.cspell.json`, оновлюючи його в алфавітному порядку.
24
+ runCspellText запускає процес класифікації неозначених слів, спочатку визначаючи їх, а потім, за умови увімкненого LLM-класифікатора, відправляючи їх на перевірку та додаючи валідні слова до словника, після чого повторно перевіряє код.
18
25
 
19
26
  ## Публічний API
20
27
 
21
- unknownWords — Збирає унікальні слова, які не були знайдені у cspell.
22
- appendWordsToDict — Додає зібрані слова до словника `.cspell.json` у відсортованому та унікальному вигляді.
23
- runCspellText — Виконує перевірку тексту за допомогою cspell для формування словника за новою схемою.
28
+ unknownWords — Збирає унікальні слова, які `cspell` виявив як незнайомі у виводі.
29
+ appendWordsToDict — Додає зібрані слова до словника `.cspell.json#words`, забезпечуючи унікальність та сортування.
30
+ runCspellText — Виконує лінтування тексту з `cspell`: класифікує слова та оновлює словник з новою схемою.
24
31
 
25
32
  ## Гарантії поведінки
26
33