@7n/llm-lib 2.0.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.0] - 2026-07-11
4
+
5
+ ### Added
6
+
7
+ - agent-fix: evidence-гейт verify-loop (Фаза A1) — opts.verify/verifyMax, фідбек провалу у ту саму сесію, телеметрія verifyAttempts
8
+
3
9
  ## [2.0.4] - 2026-07-10
4
10
 
5
11
  ### Changed
package/lib/agent-fix.mjs CHANGED
@@ -15,6 +15,13 @@
15
15
  * error, rollback }` — застосуванням володіє worker, orchestrator робить лише зовнішній
16
16
  * verdict-recheck і за провалу кличе `rollback()` (clean-slate per rung).
17
17
  *
18
+ * Evidence-гейт (дизайн 2026-07-11, Фаза A1): опційний `opts.verify` — canonical
19
+ * перевірка від consumer-а. Після prompt-у harness сам жене verify; провал →
20
+ * вивід перевірки інʼєктиться фідбеком у ТУ САМУ сесію (до `verifyMax` додаткових
21
+ * ітерацій, у межах загального `timeoutMs` рунга). Гейт структурний, а не
22
+ * model-контракт: заяви агента про успіх не важать — джерело правди лишається
23
+ * зовнішнім. Без `verify` поведінка попередня (один прохід).
24
+ *
18
25
  * Pi вантажиться lazy (top-level import модуля pi-free). Логіка інжектована через
19
26
  * `deps` для unit-тестів; `deps.astContext` — споживацький AST-екстрактор (напр.
20
27
  * oxc-based у `@nitra/cursor`), без нього tool чесно відповідає «недоступний».
@@ -46,6 +53,18 @@ const TURN_CEILING = Number(env.N_LLM_FIX_TURN_CEILING ?? env.N_CURSOR_FIX_TURN_
46
53
  */
47
54
  const DEFAULT_TIMEOUT_MS = 300_000
48
55
 
56
+ /**
57
+ * Дефолт додаткових verify-ітерацій (фідбек у ту саму сесію) при заданому `opts.verify`.
58
+ * Consumer-и тюнять per tier (local — менше, cloud — більше).
59
+ */
60
+ const VERIFY_MAX_DEFAULT = 2
61
+
62
+ /**
63
+ * Мінімальний залишок бюджету часу рунга, з яким ще є сенс запускати verify-ітерацію
64
+ * (менше — фідбек-prompt майже гарантовано не встигне, чесніше зупинитись одразу).
65
+ */
66
+ const VERIFY_MIN_BUDGET_MS = 5000
67
+
49
68
  /**
50
69
  * Порожній rollback для fail-шляхів.
51
70
  * @returns {void}
@@ -54,6 +73,21 @@ function noop() {
54
73
  /* навмисно порожньо: нема чого відкочувати */
55
74
  }
56
75
 
76
+ /**
77
+ * Будує фідбек-prompt verify-ітерації: точний вивід canonical-перевірки + нагадування
78
+ * обмежень (той самий semantic-collateral guard, що й у buildFixPrompt).
79
+ * @param {string} output вивід перевірки (порушення, що лишились)
80
+ * @returns {string} prompt для тієї самої сесії
81
+ */
82
+ export function buildVerifyFeedbackPrompt(output) {
83
+ return (
84
+ 'Перевірка правила показує, що порушення ДОСІ активне після твоїх правок:\n\n' +
85
+ `${output}\n\n` +
86
+ 'Виправ залишок. Обмеження ті самі: лише механічні зміни, лише target-файли, ' +
87
+ 'без хардкоду значень і симуляції поведінки.'
88
+ )
89
+ }
90
+
57
91
  /**
58
92
  * Маркер недоступності `astContext`.
59
93
  * @returns {{ error: string }} повідомлення про недоступність AST facts
@@ -62,6 +96,48 @@ function astUnavailable() {
62
96
  return { error: 'ast_facts недоступний: consumer не надав astContext' }
63
97
  }
64
98
 
99
+ /**
100
+ * Evidence-петля: canonical verify → (не ok) → фідбек у ТУ САМУ сесію, поки не ok /
101
+ * вичерпано `verifyMax` / вичерпано бюджет часу рунга (`timeoutMs` спільний з першим
102
+ * prompt-ом — нових таймерів не вводимо). Помилка самої перевірки — інфраструктурна:
103
+ * ітерації не палимо, чесний error. Зовнішній canonical re-detect consumer-а лишається
104
+ * джерелом правди — тут лише рання й точніша петля всередині рунга.
105
+ * @param {{ session: object, verify: (args: { touchedFiles: string[] }) => Promise<{ ok: boolean, output?: string }> | { ok: boolean, output?: string },
106
+ * verifyMax: number, timeoutMs: number, startedAt: number, clock: () => number,
107
+ * guard: { touchedFiles: () => string[] }, fixPrompt: string }} args контекст петлі.
108
+ * @returns {Promise<{ verifyAttempts: Array<{ ok: boolean, infra?: boolean }>, error: string|null }>} результат петлі.
109
+ */
110
+ async function runVerifyLoop({ session, verify, verifyMax, timeoutMs, startedAt, clock, guard, fixPrompt }) {
111
+ const verifyAttempts = []
112
+ for (let attempt = 0; ; attempt++) {
113
+ let evidence
114
+ try {
115
+ evidence = await verify({ touchedFiles: guard.touchedFiles() })
116
+ } catch (verifyError) {
117
+ verifyAttempts.push({ ok: false, infra: true })
118
+ return { verifyAttempts, error: `verify: ${verifyError.message}` }
119
+ }
120
+ verifyAttempts.push({ ok: evidence?.ok === true })
121
+ if (evidence?.ok === true) return { verifyAttempts, error: null }
122
+ if (attempt >= verifyMax) {
123
+ return { verifyAttempts, error: `verify: порушення лишилось після ${attempt + 1} перевірок` }
124
+ }
125
+ const remainingMs = timeoutMs - (clock() - startedAt)
126
+ if (remainingMs < VERIFY_MIN_BUDGET_MS) {
127
+ return { verifyAttempts, error: 'verify: бюджет часу рунга вичерпано' }
128
+ }
129
+ try {
130
+ await withTimeout(session.prompt(buildVerifyFeedbackPrompt(evidence?.output ?? '')), remainingMs, {
131
+ onTimeout: () => session.abort?.(),
132
+ label: 'fix-verify'
133
+ })
134
+ } catch (promptError) {
135
+ failOnMemoryGuard(promptError.message, fixPrompt)
136
+ return { verifyAttempts, error: promptError.message }
137
+ }
138
+ }
139
+ }
140
+
65
141
  /**
66
142
  * Будує fix-промпт для рунга: правило + порушення + (опц.) target-файли + (опц.) feedback
67
143
  * попереднього провалу + жорсткий блок обмежень (лише механічні зміни) + інструкція
@@ -172,6 +248,8 @@ async function defaultCreateSession({ registry, model, thinkingLevel, cwd, facto
172
248
  * model: string, tier?: string, feedback?: object, caller?: string, timeoutMs?: number, ruleText?: string,
173
249
  * chain?: object,
174
250
  * targetFiles?: string[],
251
+ * verify?: (args: { touchedFiles: string[] }) => Promise<{ ok: boolean, output?: string }> | { ok: boolean, output?: string },
252
+ * verifyMax?: number,
175
253
  * deps?: { createSession?: (args: object) => Promise<object>, getRegistry?: () => Promise<object>,
176
254
  * registry?: object, root?: string|null,
177
255
  * astContext?: (path: string) => object,
@@ -190,6 +268,8 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
190
268
  ruleText,
191
269
  chain = null,
192
270
  targetFiles,
271
+ verify = null,
272
+ verifyMax = VERIFY_MAX_DEFAULT,
193
273
  deps = {}
194
274
  } = opts
195
275
  const createSession = deps.createSession ?? defaultCreateSession
@@ -307,6 +387,14 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
307
387
  failOnMemoryGuard(error, fixPrompt)
308
388
  }
309
389
 
390
+ // Evidence-гейт: verify → (не ok) → фідбек у ТУ САМУ сесію (див. runVerifyLoop).
391
+ let verifyAttempts = []
392
+ if (verify && !error) {
393
+ const loop = await runVerifyLoop({ session, verify, verifyMax, timeoutMs, startedAt, clock, guard, fixPrompt })
394
+ verifyAttempts = loop.verifyAttempts
395
+ error = loop.error
396
+ }
397
+
310
398
  const touchedFiles = guard.touchedFiles()
311
399
  const telemetry = {
312
400
  rule: ruleId,
@@ -318,6 +406,7 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
318
406
  edits: guard.state.editLog,
319
407
  blocks: guard.state.blocks,
320
408
  backstopHit,
409
+ verifyAttempts,
321
410
  wallMs: clock() - startedAt
322
411
  }
323
412
  // Usage кроку для chain-агрегатів: сума по turns агентної сесії.
@@ -348,6 +437,8 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
348
437
  toolCallCount,
349
438
  touchedFiles,
350
439
  backstopHit,
440
+ verifyAttempts: verifyAttempts.length,
441
+ verifyOk: verifyAttempts.length > 0 ? verifyAttempts.at(-1).ok : null,
351
442
  wallMs: clock() - startedAt,
352
443
  error,
353
444
  ...chain?.traceFields()
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: agent-fix.mjs
4
4
  resource: llm-lib/lib/agent-fix.mjs
5
5
  docgen:
6
- crc: cd657d18
6
+ crc: 0f2be7f8
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -18,10 +18,13 @@ buildFixPrompt готує текстовий промпт, що містить
18
18
  runAgentFix виконує повний агентний цикл для спроби виправлення порушення правила, включаючи взаємодію з інструментами, застосування патча та фіксацію телеметрії.
19
19
  Виклик сесії огорнутий timeout-гонкою: `opts.timeoutMs` (дефолт 300s, коли consumer не передав значення) на спрацюванні abort-ить сесію і повертає помилку `fix timeout …` — зависла LLM-сесія (напр. мертва SSE) не блокує виклик назавжди.
20
20
 
21
+ Evidence-гейт (опційний `opts.verify`, Фаза A1 run-harness): після prompt-у модуль сам запускає canonical-перевірку consumer-а; провал інʼєктиться фідбеком у ту саму сесію — до `opts.verifyMax` додаткових ітерацій (дефолт 2), у межах того самого `timeoutMs` (залишок бюджету < 5s — чесна зупинка без ітерації). Гейт структурний: заяви агента про успіх не важать, джерелом правди лишається зовнішня перевірка. Помилка самої перевірки — інфраструктурна: ітерації не витрачаються, повертається `error` з префіксом `verify:`. Без `verify` — поведінка попередня (один прохід). Спроби фіксуються у `telemetry.verifyAttempts` і у trace (`verifyAttempts`, `verifyOk`).
22
+
21
23
  ## Публічний API
22
24
 
23
25
  buildFixPrompt — формує інструкцію для виправлення проблеми, включаючи відповідне правило, описане порушення, опційний перелік target-файлів (єдині наявні файли, які дозволено редагувати) та можливий відгук з попередньої невдалої спроби; містить обовʼязковий блок обмежень semantic-collateral guard (лише механічні зміни: без зміни бізнес-логіки, без хардкоду значень, без симуляції поведінки — spec pi-migration §12, addendum 2026-07-05) і директиву щодо кроків перед редагуванням та самоперевірки.
24
- runAgentFixвиконує єдину спробу виправлення коду з боку агента для заданого правила, використовуючи вказані параметри (модель, рівень, зворотний зв'язок, тощо) та ін'єкції для тестування.
26
+ buildVerifyFeedbackPromptформує фідбек-повідомлення verify-ітерації: точний вивід canonical-перевірки + нагадування обмежень semantic-collateral guard.
27
+ runAgentFix — виконує єдину спробу виправлення коду з боку агента для заданого правила, використовуючи вказані параметри (модель, рівень, зворотний зв'язок, verify-гейт, тощо) та ін'єкції для тестування.
25
28
 
26
29
  ## Гарантії поведінки
27
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/llm-lib",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Тонкий шар роботи з LLM (локальні omlx + хмарні провайдери) поверх pi: model tiers, one-shot, agentic-раннери, write-guard, trace, telemetry, prompt-budget",
5
5
  "keywords": [
6
6
  "nitra",