@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
@@ -1,346 +0,0 @@
1
- /** @see ./docs/llm-worker.md */
2
-
3
- import { existsSync, readdirSync, readFileSync } from 'node:fs'
4
- import { join } from 'node:path'
5
- import { env } from 'node:process'
6
- import { resolveModel } from '../../../lib/models.mjs'
7
- import { callLlmRich } from '../../../lib/llm.mjs'
8
- import { applyChanges, parseChangesResponse, readFilesForFix } from './llm-fix-apply.mjs'
9
-
10
- // Дефолтна модель, коли викликач не задав `opts.model` (legacy/прямі виклики).
11
- // Драбина ескалації (`orchestrator.mjs`) завжди передає модель рунга явно, тож
12
- // тут лишається тільки fallback на min-тир. Перевизначення — `N_CURSOR_FIX_MODEL`.
13
- const MODEL = env.N_CURSOR_FIX_MODEL ?? resolveModel('min')
14
-
15
- // Бюджет thinking-токенів для omlx-моделей (Gemma 4 та ін., що підтримують thinking_budget).
16
- // Значення 0 вимикає thinking. Перевизначення — `N_CURSOR_OMLX_THINKING_BUDGET`.
17
- const DEFAULT_THINKING_BUDGET = Number(env.N_CURSOR_OMLX_THINKING_BUDGET ?? 4096)
18
-
19
- const API_KEY_RE = /api key/i
20
-
21
- const FILE_EXTS = 'json|js|mjs|ts|vue|yml|yaml|toml|mdc|md|sh|py'
22
-
23
- /**
24
- * Каталог `npm/rules/` у пакеті — для вибору sub-check .mdc.
25
- * Шлях: <package>/npm/scripts/lib/fix/ → ../../.. → npm/ → rules/.
26
- */
27
- const PACKAGE_RULES_DIR = join(import.meta.dirname, '..', '..', '..', 'rules')
28
-
29
- /**
30
- * Витягує шляхи файлів лише з рядків ❌ у violation output.
31
- * Без workspace-розгортання — повертає bare path для звірки з target.json.
32
- * @param {string} output violation output
33
- * @returns {string[]} унікальні шляхи з ❌-рядків
34
- */
35
- function extractFailPaths(output) {
36
- const seen = new Set()
37
- const add = p => {
38
- seen.add(p)
39
- }
40
- const failSep = `(?::\\d+)?(?::\\s|[\\s—]|$)`
41
- // ❌ [ws] path/file.ext → strip workspace, зберігаємо bare file
42
- const failWsRe = new RegExp(`^\\s*❌\\s+\\[[\\w-]+\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
43
- for (const m of output.matchAll(failWsRe)) add(m[1])
44
- const failRe = new RegExp(`^\\s*❌\\s+(\\.?[\\w][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
45
- for (const m of output.matchAll(failRe)) add(m[1])
46
- return [...seen]
47
- }
48
-
49
- /**
50
- * Збирає .mdc-контекст правила з двох джерел у пакеті (`PACKAGE_RULES_DIR`):
51
- * - `js/<check>.mdc` — описи check-логіки (завжди всі, відсортовані за іменем);
52
- * - `policy/<c>/<c>.mdc` — concern-специфічний контент: вибираємо лише ті concern/.mdc,
53
- * чий `target.json → files.single` збігається з failing paths
54
- * у violation output; якщо жоден не збігається — включаємо всі.
55
- * @param {string} ruleId ID правила
56
- * @param {string} violationOutput violation output
57
- * @returns {string|null} конкатенація зібраних .mdc або null, якщо нічого не знайдено
58
- */
59
- function readRuleMdc(ruleId, violationOutput) {
60
- const ruleDir = join(PACKAGE_RULES_DIR, ruleId)
61
- const parts = []
62
-
63
- // 1. js/**/*.mdc — завжди всі
64
- const jsDir = join(ruleDir, 'js')
65
- if (existsSync(jsDir)) {
66
- let jsFiles
67
- try {
68
- jsFiles = readdirSync(jsDir)
69
- .filter(f => f.endsWith('.mdc'))
70
- .sort()
71
- } catch {
72
- jsFiles = []
73
- }
74
- for (const f of jsFiles) parts.push(readFileSync(join(jsDir, f), 'utf8').trim())
75
- }
76
-
77
- // 2. policy/**/*.mdc — matched через target.json; fallback — всі
78
- const policyDir = join(ruleDir, 'policy')
79
- if (existsSync(policyDir)) {
80
- const failPaths = extractFailPaths(violationOutput)
81
- let concerns
82
- try {
83
- concerns = readdirSync(policyDir, { withFileTypes: true })
84
- } catch {
85
- concerns = []
86
- }
87
-
88
- const all = []
89
- const matched = []
90
- for (const entry of concerns) {
91
- if (!entry.isDirectory()) continue
92
- const concernDir = join(policyDir, entry.name)
93
- const mdcEntry = readdirSync(concernDir).find(f => f.endsWith('.mdc'))
94
- if (!mdcEntry) continue
95
- const content = readFileSync(join(concernDir, mdcEntry), 'utf8').trim()
96
- all.push(content)
97
-
98
- const targetPath = join(concernDir, 'target.json')
99
- if (!existsSync(targetPath)) continue
100
- let target
101
- try {
102
- target = JSON.parse(readFileSync(targetPath, 'utf8'))
103
- } catch {
104
- continue
105
- }
106
- const targetFile = target?.files?.single
107
- if (!targetFile) continue
108
- if (failPaths.some(p => p === targetFile || p.endsWith(`/${targetFile}`))) matched.push(content)
109
- }
110
-
111
- parts.push(...(matched.length > 0 ? matched : all))
112
- }
113
-
114
- return parts.length > 0 ? parts.join('\n\n') : null
115
- }
116
-
117
- /**
118
- * Витягує відносні шляхи файлів із violation output.
119
- * Розуміє workspace-prefix: `[npm] skills/foo.mjs` → `npm/skills/foo.mjs`.
120
- * Спочатку явно парсить рядки ❌ (найвищий сигнал — файл потребує фіксу),
121
- * потім підхоплює решту файлів generic-regex (контекст для читання).
122
- * @param {string} output violation output з fix check
123
- * @returns {string[]} унікальні відносні шляхи (від кореня проєкту)
124
- */
125
- export function extractFilePaths(output) {
126
- const seen = new Set()
127
- const results = []
128
- const add = p => {
129
- if (!seen.has(p)) {
130
- seen.add(p)
131
- results.push(p)
132
- }
133
- }
134
-
135
- // 1. Явні рядки ❌ — найвищий сигнал: саме ці файли потребують фіксу.
136
- // Формати: `❌ [ws] path/file.ext:line — msg` та `❌ path/file.ext: msg`
137
- // Роздільник після шляху: `:` (з пробілом або цифрою), `—` (em-dash), або кінець рядка.
138
- const failSep = `(?::\\d+)?(?::\\s|[\\s—]|$)`
139
- const failWsRe = new RegExp(`^\\s*❌\\s+\\[([\\w-]+)\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
140
- for (const m of output.matchAll(failWsRe)) add(`${m[1]}/${m[2]}`)
141
-
142
- const failRe = new RegExp(`^\\s*❌\\s+(\\.?[\\w][\\w./-]*\\.(?:${FILE_EXTS}))${failSep}`, 'gm')
143
- for (const m of output.matchAll(failRe)) add(m[1])
144
-
145
- // 2. Generic-regex: підхоплює файли з ✅-рядків та описів (контекст для читання).
146
- // Workspace: [npm] skills/foo.mjs
147
- const wsRe = new RegExp(`\\[([\\w-]+)\\]\\s+([\\w./][\\w./-]*\\.(?:${FILE_EXTS}))(?::\\d+)?`, 'gm')
148
- for (const m of output.matchAll(wsRe)) add(`${m[1]}/${m[2]}`)
149
-
150
- // Без workspace: path/to/file.ext або ./file.ext
151
- const re = new RegExp(`(?:^|\\s)(\\.?\\w[\\w./-]*\\.(?:${FILE_EXTS}))(?::\\d+)?`, 'gm')
152
- for (const m of output.matchAll(re)) add(m[1])
153
-
154
- return results
155
- }
156
-
157
- /**
158
- * Будує prompt для pi: правило + порушення + поточний вміст файлів.
159
- * Будує опційний feedback-блок драбини ескалації: попередній рунг застосував
160
- * зміни, але re-check лишився червоним. Просимо модель спершу (в полі `diagnosis`)
161
- * сформулювати, **чому** попередня спроба не задовольнила правило, тоді виправити.
162
- * @param {{ previousModel?: string, previousChanges?: Array<{path:string}>, previousError?: string|null } | null} feedback контекст попереднього рунга
163
- * @returns {string[]} рядки prompt-блоку (порожній масив, якщо feedback немає)
164
- */
165
- function buildFeedbackBlock(feedback) {
166
- if (!feedback) return []
167
- const changedPaths = (feedback.previousChanges ?? []).map(c => c.path).filter(Boolean)
168
- return [
169
- ``,
170
- `A PREVIOUS attempt (model: ${feedback.previousModel || 'pi'}) did NOT resolve this violation.`,
171
- changedPaths.length > 0
172
- ? `Previously changed files: ${changedPaths.join(', ')}`
173
- : `The previous attempt produced no usable changes.`,
174
- feedback.previousError ? `Previous attempt error: ${feedback.previousError}` : ``,
175
- `The violation output below is what STILL fails after that attempt.`,
176
- `In the "diagnosis" field, briefly state WHY the previous attempt failed, then provide a corrected fix.`
177
- ].filter(line => line !== ``)
178
- }
179
-
180
- /**
181
- * @param {string} ruleId ID правила
182
- * @param {string} ruleMdc вміст .mdc-файлу правила
183
- * @param {string} output violation output
184
- * @param {Array<{path:string, content:string}>} files прочитані файли (path + content)
185
- * @param {{ previousModel?: string, previousChanges?: Array<{path:string}>, previousError?: string|null } | null} [feedback] контекст попереднього рунга драбини (для retry-with-feedback)
186
- * @returns {string} текст промпта для pi
187
- */
188
- function buildPrompt(ruleId, ruleMdc, output, files, feedback = null) {
189
- const filesBlock =
190
- files.length === 0
191
- ? '(no files identified)'
192
- : files.map(f => `<file path="${f.path}">\n${f.content}\n</file>`).join('\n\n')
193
-
194
- return [
195
- `You fix project structure violations. Return ONLY valid JSON — no explanation, no markdown.`,
196
- ...buildFeedbackBlock(feedback),
197
- ``,
198
- `Rule (n-${ruleId}.mdc):`,
199
- `---`,
200
- ruleMdc,
201
- `---`,
202
- ``,
203
- `Violation output:`,
204
- output,
205
- ``,
206
- `Current file contents:`,
207
- filesBlock,
208
- ``,
209
- `Return JSON with this exact shape:`,
210
- `{"diagnosis":"short reason the rule fails (or why prior attempt failed); empty string if first attempt","changes":[{"path":"relative/path/to/file","content":"full corrected file content"}]}`,
211
- ``,
212
- `Rules:`,
213
- `- "path" is relative to the project root`,
214
- `- "content" is the complete new file content (not a diff)`,
215
- `- Only include files that actually need to change`,
216
- `- "diagnosis" is plain text inside the JSON — do NOT emit prose outside the JSON`,
217
- `- If nothing can be fixed automatically, return {"diagnosis":"...","changes":[],"error":"reason"}`
218
- ].join('\n')
219
- }
220
-
221
- /**
222
- * Викликає LLM через `callLlmRich` (маршрут за префіксом model-id; wire-trace).
223
- * Повертає reasoning поряд із текстом — для verbose-блоку оркестратора.
224
- * Зберігає дружнє повідомлення про відсутній API-ключ для хмарних провайдерів.
225
- * @param {string} prompt текст промпта
226
- * @param {string} model назва моделі (provider/id, `omlx/...` або '')
227
- * @param {string} caller мітка викликача для wire-trace (`fix:<rule>:<rung>`)
228
- * @param {number} [timeoutMs] ліміт виклику (драбина задає per-tier; undefined → дефолт callLlmRich)
229
- * @param {number} [thinkingBudget] бюджет thinking-токенів (лише omlx; 0 = вимкнено)
230
- * @returns {{ text: string, reasoning: string|null, reasoningSource: string|null, error?: string }}
231
- */
232
- function callModel(prompt, model, caller, timeoutMs, thinkingBudget) {
233
- try {
234
- const { content, reasoning, reasoningSource } = callLlmRich([{ role: 'user', content: prompt }], model, {
235
- timeoutMs,
236
- caller,
237
- thinkingBudget
238
- })
239
- return { text: content, reasoning, reasoningSource }
240
- } catch (error) {
241
- const msg = String(error.message)
242
- if (API_KEY_RE.test(msg)) {
243
- const provider = model ? model.split('/')[0] : 'дефолтного провайдера'
244
- return {
245
- text: '',
246
- reasoning: null,
247
- reasoningSource: null,
248
- error: [
249
- `pi: немає ключа для ${provider}.`,
250
- `Встановіть N_CLOUD_MIN_MODEL=provider/model-id`,
251
- `(напр.: openai/gpt-5.4-mini, google/gemini-2.5-flash, ollama/gemma3:4b)`
252
- ].join(' ')
253
- }
254
- }
255
- return { text: '', reasoning: null, reasoningSource: null, error: msg }
256
- }
257
- }
258
-
259
- /**
260
- * LLM-worker: виправляє одне rule-порушення через pi (C1 pattern).
261
- * Повертає `changes`/`diagnosis` навіть при невдачі — драбина ескалації
262
- * (`orchestrator.mjs`) логує їх і прокидає як feedback у наступний рунг.
263
- * Поля `reasoning`/`reasoningSource`/`promptSummary` використовує оркестратор
264
- * для verbose-блоку після кожного рунга (`--full` режим).
265
- * @param {string} ruleId ID правила
266
- * @param {string} violationOutput output з fix check для цього rule
267
- * @param {string} projectRoot абсолютний шлях до кореня проєкту
268
- * @param {{ model?: string, feedback?: object|null, caller?: string, timeoutMs?: number, thinkingBudget?: number }} opts опції:
269
- * `model` — перевизначення моделі; `feedback` — контекст попереднього рунга
270
- * драбини (retry-with-feedback); `caller` — мітка для wire-trace; `timeoutMs` —
271
- * per-tier ліміт виклику (драбина: локалі fail-fast, хмара повний);
272
- * `thinkingBudget` — кількість thinking-токенів для omlx (дефолт `DEFAULT_THINKING_BUDGET`).
273
- * `timeoutMs` — per-tier ліміт: локальні 300s (4b повільна, backstop — turn-ceiling), хмарні 120s.
274
- * @returns {{ ok: boolean, error?: string, changes: Array<{path:string}>, diagnosis: string|null, reasoning: string|null, reasoningSource: string|null, promptSummary: object }}
275
- */
276
- export function runLlmWorker(ruleId, violationOutput, projectRoot, opts = {}) {
277
- const model = opts.model ?? MODEL
278
- const feedback = opts.feedback ?? null
279
- const caller = opts.caller ?? 'fix'
280
- const timeoutMs = opts.timeoutMs
281
- const thinkingBudget = opts.thinkingBudget ?? DEFAULT_THINKING_BUDGET
282
-
283
- // 1. Читаємо rule .mdc з джерела пакету: js/**/*.mdc + policy/**/*.mdc.
284
- const ruleMdc = readRuleMdc(ruleId, violationOutput) ?? '(rule file not found)'
285
-
286
- // 2. Витягуємо файли з violation output і читаємо їх
287
- const files = readFilesForFix(extractFilePaths(violationOutput), projectRoot)
288
-
289
- // 3. Будуємо summary промпту (для verbose-блоку) до виклику моделі
290
- const promptSummary = {
291
- ruleMdcLen: ruleMdc.length,
292
- violationLen: violationOutput.length,
293
- filesCount: files.length,
294
- filesTotalBytes: files.reduce((s, f) => s + f.content.length, 0),
295
- hasFeedback: !!feedback,
296
- feedbackModel: feedback?.previousModel ?? null,
297
- feedbackChangesCount: feedback?.previousChanges?.length ?? 0,
298
- feedbackError: feedback?.previousError ?? null
299
- }
300
-
301
- // 4. Будуємо prompt і викликаємо модель
302
- const prompt = buildPrompt(ruleId, ruleMdc, violationOutput, files, feedback)
303
- const {
304
- text,
305
- error: modelError,
306
- reasoning,
307
- reasoningSource
308
- } = callModel(prompt, model, caller, timeoutMs, thinkingBudget)
309
-
310
- if (modelError)
311
- return { ok: false, error: modelError, changes: [], diagnosis: null, reasoning, reasoningSource, promptSummary }
312
- if (!text)
313
- return {
314
- ok: false,
315
- error: 'model returned empty response',
316
- changes: [],
317
- diagnosis: null,
318
- reasoning,
319
- reasoningSource,
320
- promptSummary
321
- }
322
-
323
- // 5. Парсимо відповідь
324
- const parsed = parseChangesResponse(text)
325
- if (!parsed) {
326
- return {
327
- ok: false,
328
- error: `cannot parse pi response: ${text.slice(0, 200)}`,
329
- changes: [],
330
- diagnosis: null,
331
- reasoning,
332
- reasoningSource,
333
- promptSummary
334
- }
335
- }
336
- const diagnosis = typeof parsed.diagnosis === 'string' && parsed.diagnosis ? parsed.diagnosis : null
337
- const changes = parsed.changes ?? []
338
- if (parsed.error)
339
- return { ok: false, error: parsed.error, changes, diagnosis, reasoning, reasoningSource, promptSummary }
340
- if (changes.length === 0)
341
- return { ok: false, error: 'pi returned no changes', changes, diagnosis, reasoning, reasoningSource, promptSummary }
342
-
343
- // 6. Застосовуємо зміни
344
- const applied = applyChanges(changes, projectRoot)
345
- return { ...applied, changes, diagnosis, reasoning, reasoningSource, promptSummary }
346
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * Verbose-блок після кожного LLM-рунга у `--full` режимі.
3
- * Друкує стислий опис промпту і thinking-монолог моделі (якщо є).
4
- * Вимикається через `N_CURSOR_FIX_VERBOSE=off`.
5
- */
6
-
7
- const THINKING_PREVIEW_LEN = 500
8
-
9
- /**
10
- * Форматує рядок файлів для prompt-блоку.
11
- * @param {number} count кількість файлів
12
- * @param {number} totalBytes сумарний розмір у байтах
13
- * @returns {string}
14
- */
15
- function formatFiles(count, totalBytes) {
16
- if (count === 0) return '(none)'
17
- const kb = (totalBytes / 1024).toFixed(1)
18
- const word = count === 1 ? 'файл' : count < 5 ? 'файли' : 'файлів'
19
- return `${count} ${word} (${kb} KB)`
20
- }
21
-
22
- /**
23
- * Форматує рядок feedback для prompt-блоку.
24
- * @param {boolean} hasFeedback чи є feedback
25
- * @param {string|null} feedbackModel модель попереднього рунга
26
- * @param {number} feedbackChangesCount кількість змін попереднього рунга
27
- * @param {string|null} feedbackError помилка попереднього рунга
28
- * @returns {string}
29
- */
30
- function formatFeedback(hasFeedback, feedbackModel, feedbackChangesCount, feedbackError) {
31
- if (!hasFeedback) return '(none)'
32
- const parts = []
33
- if (feedbackModel) parts.push(`model=${feedbackModel}`)
34
- parts.push(`${feedbackChangesCount} changes`)
35
- if (feedbackError) parts.push(`error="${feedbackError.slice(0, 60)}"`)
36
- return parts.join(', ')
37
- }
38
-
39
- /**
40
- * Друкує verbose-блок після рядка рунга (`⚡`/`✅`).
41
- * Містить стислий опис промпту і thinking-монолог моделі (якщо є).
42
- * Не викликається якщо `N_CURSOR_FIX_VERBOSE=off`.
43
- * @param {string} ruleId ID правила
44
- * @param {{ ruleMdcLen: number, violationLen: number, filesCount: number, filesTotalBytes: number, hasFeedback: boolean, feedbackModel: string|null, feedbackChangesCount: number, feedbackError: string|null }} promptSummary стислий опис промпту
45
- * @param {string|null} reasoning thinking-монолог моделі
46
- * @param {string|null} reasoningSource джерело reasoning ('field'|'think_tag'|'truncated'|null)
47
- */
48
- export function printVerboseBlock(ruleId, promptSummary, reasoning, reasoningSource) {
49
- const {
50
- ruleMdcLen,
51
- violationLen,
52
- filesCount,
53
- filesTotalBytes,
54
- hasFeedback,
55
- feedbackModel,
56
- feedbackChangesCount,
57
- feedbackError
58
- } = promptSummary
59
-
60
- console.log(``)
61
- console.log(` prompt:`)
62
- console.log(` rule: n-${ruleId}.mdc (${ruleMdcLen} chars)`)
63
- console.log(` violation: ${violationLen} chars`)
64
- console.log(` files: ${formatFiles(filesCount, filesTotalBytes)}`)
65
- console.log(` feedback: ${formatFeedback(hasFeedback, feedbackModel, feedbackChangesCount, feedbackError)}`)
66
-
67
- if (reasoning) {
68
- const preview =
69
- reasoning.length > THINKING_PREVIEW_LEN
70
- ? reasoning.slice(0, THINKING_PREVIEW_LEN) + ` … (+${reasoning.length - THINKING_PREVIEW_LEN} chars)`
71
- : reasoning
72
- console.log(``)
73
- console.log(` thinking [${reasoningSource}, ${reasoning.length} chars]:`)
74
- for (const line of preview.split('\n')) {
75
- console.log(` ${line}`)
76
- }
77
- } else {
78
- console.log(``)
79
- console.log(` thinking: (none)`)
80
- }
81
- console.log(``)
82
- }