micro-models-agent 0.18.3 → 0.19.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/dist/core/bootstrap.js +5 -12
- package/dist/modules/hallucination/confidence.js +7 -2
- package/dist/modules/hallucination/consistency.js +12 -6
- package/dist/modules/hallucination/detector.js +10 -18
- package/dist/modules/hallucination/factual.js +401 -10
- package/dist/modules/hallucination/index.js +3 -4
- package/dist/tools/create-dir.js +0 -1
- package/dist/tools/delete-file.js +0 -1
- package/dist/tools/read-file.js +0 -3
- package/dist/tools/write-file.js +1 -2
- package/package.json +1 -1
package/dist/core/bootstrap.js
CHANGED
|
@@ -34,10 +34,9 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
34
34
|
`Workspace: ${baseDir}`,
|
|
35
35
|
`${profileCompressed}`,
|
|
36
36
|
`Reply in the user's language. Use tools for filesystem, bash, web access.`,
|
|
37
|
-
`
|
|
38
|
-
`
|
|
39
|
-
`If
|
|
40
|
-
`If a tool call fails (e.g., a skill is too large), try an alternative approach or continue without it. Do not reply with empty text when the task is unfinished.`,
|
|
37
|
+
`Use tools when needed. Explain briefly what you're doing if it's not obvious.`,
|
|
38
|
+
`Answer the user's question directly. Only take action when the user explicitly asks you to change, create, or fix something.`,
|
|
39
|
+
`If a tool call fails, report the error and ask the user how to proceed.`,
|
|
41
40
|
``,
|
|
42
41
|
`Design principles — apply them automatically without naming them:`,
|
|
43
42
|
`- Do not add code, files, or abstractions that are not needed right now (YAGNI — You Ain't Gonna Need It). If something is not required by the current task, omit it.`,
|
|
@@ -49,7 +48,7 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
49
48
|
}
|
|
50
49
|
lines.push(``, `Bash tool rules:`, `- Use the "workdir" parameter to run commands in a specific directory. Do NOT chain "cd dir && cmd" — the security module blocks the "&&" operator.`, `- Run one command per tool call. Split multi-step shell operations into separate bash calls.`);
|
|
51
50
|
if (config.autoPlan) {
|
|
52
|
-
lines.push(``, `Plan rule: For
|
|
51
|
+
lines.push(``, `Plan rule: For complex tasks with 3+ steps, you may create a plan using the "plan" tool. For simple questions, just answer directly.`);
|
|
53
52
|
}
|
|
54
53
|
const hasMCP = config.mcpServers &&
|
|
55
54
|
Object.values(config.mcpServers).some((s) => s.enabled !== false);
|
|
@@ -187,12 +186,6 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
187
186
|
toolCtx.llmProvider = llmProvider;
|
|
188
187
|
toolCtx.toolExecutor = toolExecutor;
|
|
189
188
|
const hallucinationDetector = new HallucinationDetector();
|
|
190
|
-
const factualCheck = hallucinationDetector.getFactualCheck();
|
|
191
|
-
factualCheck.setBaseDir(baseDir);
|
|
192
|
-
toolCtx.trackReadPath = (p) => factualCheck.trackReadPath(p);
|
|
193
|
-
toolCtx.trackCreatedPath = (p) => factualCheck.trackCreatedPath(p);
|
|
194
|
-
toolCtx.trackDeletedPath = (p) => factualCheck.trackDeletedPath(p);
|
|
195
|
-
toolCtx.trackDocumentContent = (c) => factualCheck.trackDocumentContent(c);
|
|
196
189
|
const moduleRegistry = new ModuleRegistry();
|
|
197
190
|
const execModule = new ExecutionModule(baseDir, config.stuckThreshold);
|
|
198
191
|
moduleRegistry.register(execModule);
|
|
@@ -203,7 +196,7 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
|
|
|
203
196
|
const mcpModule = new MCPModule(config);
|
|
204
197
|
await mcpModule.initialize();
|
|
205
198
|
moduleRegistry.register(mcpModule);
|
|
206
|
-
const memoryModule = new MemoryModule(join(dir,
|
|
199
|
+
const memoryModule = new MemoryModule(join(dir, "memory"));
|
|
207
200
|
moduleRegistry.register(memoryModule);
|
|
208
201
|
if (config.browser.enabled) {
|
|
209
202
|
const browserModule = new BrowserModule();
|
|
@@ -30,14 +30,19 @@ export class ConfidenceCheck {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
// Language-agnostic: very low word diversity (same words repeated)
|
|
33
|
-
const words = response
|
|
33
|
+
const words = response
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.split(/\s+/)
|
|
36
|
+
.filter((w) => w.length > 2);
|
|
34
37
|
if (words.length >= 10) {
|
|
35
38
|
const unique = new Set(words);
|
|
36
39
|
const diversity = unique.size / words.length;
|
|
37
40
|
if (diversity < 0.25) {
|
|
38
41
|
return {
|
|
39
42
|
status: "warn",
|
|
40
|
-
reason: t("hall.repetitive", {
|
|
43
|
+
reason: t("hall.repetitive", {
|
|
44
|
+
pct: Math.round((1 - diversity) * 100),
|
|
45
|
+
}),
|
|
41
46
|
};
|
|
42
47
|
}
|
|
43
48
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t } from
|
|
1
|
+
import { t } from "../../i18n/index";
|
|
2
2
|
export class ConsistencyCheck {
|
|
3
3
|
decisions = [];
|
|
4
4
|
createdFiles = new Set();
|
|
@@ -18,8 +18,11 @@ export class ConsistencyCheck {
|
|
|
18
18
|
validate(response) {
|
|
19
19
|
const lower = response.toLowerCase();
|
|
20
20
|
for (const d of this.decisions) {
|
|
21
|
-
const decisionWords = d.decision
|
|
22
|
-
|
|
21
|
+
const decisionWords = d.decision
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.split(/\s+/)
|
|
24
|
+
.filter((w) => w.length > 3);
|
|
25
|
+
const contradicts = decisionWords.some((word) => {
|
|
23
26
|
// English patterns
|
|
24
27
|
if (lower.includes(`instead of ${word}`))
|
|
25
28
|
return true;
|
|
@@ -50,11 +53,14 @@ export class ConsistencyCheck {
|
|
|
50
53
|
});
|
|
51
54
|
if (contradicts) {
|
|
52
55
|
return {
|
|
53
|
-
status:
|
|
54
|
-
reason: t(
|
|
56
|
+
status: "warn",
|
|
57
|
+
reason: t("hall.contradiction", {
|
|
58
|
+
decision: d.decision,
|
|
59
|
+
location: d.location,
|
|
60
|
+
}),
|
|
55
61
|
};
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
|
-
return { status:
|
|
64
|
+
return { status: "pass" };
|
|
59
65
|
}
|
|
60
66
|
}
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ConfidenceCheck } from './confidence';
|
|
1
|
+
import { ConsistencyCheck } from "./consistency";
|
|
2
|
+
import { ConfidenceCheck } from "./confidence";
|
|
4
3
|
export class HallucinationDetector {
|
|
5
|
-
factual;
|
|
6
4
|
consistency;
|
|
7
5
|
confidence;
|
|
8
6
|
constructor() {
|
|
9
|
-
this.factual = new FactualCheck();
|
|
10
7
|
this.consistency = new ConsistencyCheck();
|
|
11
8
|
this.confidence = new ConfidenceCheck();
|
|
12
9
|
}
|
|
13
|
-
getFactualCheck() {
|
|
14
|
-
return this.factual;
|
|
15
|
-
}
|
|
16
10
|
getConsistencyCheck() {
|
|
17
11
|
return this.consistency;
|
|
18
12
|
}
|
|
@@ -21,21 +15,19 @@ export class HallucinationDetector {
|
|
|
21
15
|
}
|
|
22
16
|
validate(response) {
|
|
23
17
|
const confidenceResult = this.confidence.validate(response);
|
|
24
|
-
if (confidenceResult.status ===
|
|
18
|
+
if (confidenceResult.status === "retry" ||
|
|
19
|
+
confidenceResult.status === "block") {
|
|
25
20
|
return confidenceResult;
|
|
26
21
|
}
|
|
27
|
-
const factualResult = this.factual.validate(response);
|
|
28
22
|
const consistencyResult = this.consistency.validate(response);
|
|
29
23
|
const warnings = [];
|
|
30
|
-
if (
|
|
31
|
-
warnings.push(
|
|
32
|
-
if (
|
|
33
|
-
warnings.push(
|
|
34
|
-
if (confidenceResult.status === 'warn')
|
|
35
|
-
warnings.push(confidenceResult.reason || '');
|
|
24
|
+
if (consistencyResult.status === "warn")
|
|
25
|
+
warnings.push(consistencyResult.reason || "");
|
|
26
|
+
if (confidenceResult.status === "warn")
|
|
27
|
+
warnings.push(confidenceResult.reason || "");
|
|
36
28
|
if (warnings.length > 0) {
|
|
37
|
-
return { status:
|
|
29
|
+
return { status: "warn", reason: warnings.join("; ") };
|
|
38
30
|
}
|
|
39
|
-
return { status:
|
|
31
|
+
return { status: "pass" };
|
|
40
32
|
}
|
|
41
33
|
}
|
|
@@ -55,14 +55,379 @@ const FILE_EXTENSIONS = new Set([
|
|
|
55
55
|
]);
|
|
56
56
|
const VERSION_PATTERN = /^\d+(\.\d+)*$/;
|
|
57
57
|
const COMMON_WORDS = new Set([
|
|
58
|
-
"node.js",
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
58
|
+
"node.js",
|
|
59
|
+
"Node.js",
|
|
60
|
+
"console.log",
|
|
61
|
+
"console.error",
|
|
62
|
+
"console.warn",
|
|
63
|
+
"console.info",
|
|
64
|
+
"Math.floor",
|
|
65
|
+
"Math.ceil",
|
|
66
|
+
"Math.round",
|
|
67
|
+
"Math.max",
|
|
68
|
+
"Math.min",
|
|
69
|
+
"JSON.parse",
|
|
70
|
+
"JSON.stringify",
|
|
71
|
+
"Object.keys",
|
|
72
|
+
"Object.values",
|
|
73
|
+
"Object.entries",
|
|
74
|
+
"Array.from",
|
|
75
|
+
"Array.isArray",
|
|
76
|
+
"Date.now",
|
|
77
|
+
"Date.parse",
|
|
78
|
+
"RegExp",
|
|
79
|
+
"Promise",
|
|
80
|
+
]);
|
|
81
|
+
/**
|
|
82
|
+
* Words that indicate the model is talking about a file or file operation.
|
|
83
|
+
* Used to filter out false positives where a dotted term is just a
|
|
84
|
+
* technology name (e.g. "React/Next.js component") rather than a file path.
|
|
85
|
+
*
|
|
86
|
+
* Includes base forms plus common English and Russian conjugations.
|
|
87
|
+
*/
|
|
88
|
+
const FILE_OPERATION_CONTEXT = new Set([
|
|
89
|
+
// English file operation verbs
|
|
90
|
+
"create",
|
|
91
|
+
"created",
|
|
92
|
+
"creating",
|
|
93
|
+
"update",
|
|
94
|
+
"updated",
|
|
95
|
+
"updating",
|
|
96
|
+
"edit",
|
|
97
|
+
"edited",
|
|
98
|
+
"editing",
|
|
99
|
+
"modify",
|
|
100
|
+
"modified",
|
|
101
|
+
"modifying",
|
|
102
|
+
"change",
|
|
103
|
+
"changed",
|
|
104
|
+
"changing",
|
|
105
|
+
"delete",
|
|
106
|
+
"deleted",
|
|
107
|
+
"deleting",
|
|
108
|
+
"remove",
|
|
109
|
+
"removed",
|
|
110
|
+
"removing",
|
|
111
|
+
"write",
|
|
112
|
+
"wrote",
|
|
113
|
+
"written",
|
|
114
|
+
"writing",
|
|
115
|
+
"read",
|
|
116
|
+
"reading",
|
|
117
|
+
"check",
|
|
118
|
+
"checked",
|
|
119
|
+
"checking",
|
|
120
|
+
"open",
|
|
121
|
+
"opened",
|
|
122
|
+
"opening",
|
|
123
|
+
"fix",
|
|
124
|
+
"fixed",
|
|
125
|
+
"fixing",
|
|
126
|
+
"rename",
|
|
127
|
+
"renamed",
|
|
128
|
+
"renaming",
|
|
129
|
+
"move",
|
|
130
|
+
"moved",
|
|
131
|
+
"moving",
|
|
132
|
+
"copy",
|
|
133
|
+
"copied",
|
|
134
|
+
"copying",
|
|
135
|
+
"make",
|
|
136
|
+
"made",
|
|
137
|
+
"making",
|
|
138
|
+
"build",
|
|
139
|
+
"built",
|
|
140
|
+
"building",
|
|
141
|
+
"run",
|
|
142
|
+
"ran",
|
|
143
|
+
"running",
|
|
144
|
+
"test",
|
|
145
|
+
"tested",
|
|
146
|
+
"testing",
|
|
147
|
+
"import",
|
|
148
|
+
"imported",
|
|
149
|
+
"importing",
|
|
150
|
+
"export",
|
|
151
|
+
"exported",
|
|
152
|
+
"exporting",
|
|
153
|
+
// English file nouns
|
|
154
|
+
"file",
|
|
155
|
+
"files",
|
|
156
|
+
"path",
|
|
157
|
+
"paths",
|
|
158
|
+
"directory",
|
|
159
|
+
"directories",
|
|
160
|
+
"folder",
|
|
161
|
+
"folders",
|
|
162
|
+
"module",
|
|
163
|
+
"modules",
|
|
164
|
+
"script",
|
|
165
|
+
"scripts",
|
|
166
|
+
"source",
|
|
167
|
+
"sources",
|
|
168
|
+
"code",
|
|
169
|
+
// Russian file operation verbs
|
|
170
|
+
"создать",
|
|
171
|
+
"создал",
|
|
172
|
+
"создала",
|
|
173
|
+
"создало",
|
|
174
|
+
"создаю",
|
|
175
|
+
"создаем",
|
|
176
|
+
"создает",
|
|
177
|
+
"создан",
|
|
178
|
+
"создание",
|
|
179
|
+
"создается",
|
|
180
|
+
"обновить",
|
|
181
|
+
"обновил",
|
|
182
|
+
"обновила",
|
|
183
|
+
"обновило",
|
|
184
|
+
"обновляю",
|
|
185
|
+
"обновляем",
|
|
186
|
+
"обновляет",
|
|
187
|
+
"обновлен",
|
|
188
|
+
"обновление",
|
|
189
|
+
"обновляется",
|
|
190
|
+
"изменить",
|
|
191
|
+
"изменил",
|
|
192
|
+
"изменила",
|
|
193
|
+
"изменило",
|
|
194
|
+
"изменяю",
|
|
195
|
+
"изменяем",
|
|
196
|
+
"изменяет",
|
|
197
|
+
"изменен",
|
|
198
|
+
"изменение",
|
|
199
|
+
"изменяется",
|
|
200
|
+
"редактировать",
|
|
201
|
+
"редактировал",
|
|
202
|
+
"редактировала",
|
|
203
|
+
"редактирую",
|
|
204
|
+
"редактируем",
|
|
205
|
+
"редактирует",
|
|
206
|
+
"редактирован",
|
|
207
|
+
"редактирование",
|
|
208
|
+
"редактируется",
|
|
209
|
+
"удалить",
|
|
210
|
+
"удалил",
|
|
211
|
+
"удалила",
|
|
212
|
+
"удалило",
|
|
213
|
+
"удаляю",
|
|
214
|
+
"удаляем",
|
|
215
|
+
"удаляет",
|
|
216
|
+
"удален",
|
|
217
|
+
"удаление",
|
|
218
|
+
"удаляется",
|
|
219
|
+
"добавить",
|
|
220
|
+
"добавил",
|
|
221
|
+
"добавила",
|
|
222
|
+
"добавило",
|
|
223
|
+
"добавляю",
|
|
224
|
+
"добавляем",
|
|
225
|
+
"добавляет",
|
|
226
|
+
"добавлен",
|
|
227
|
+
"добавление",
|
|
228
|
+
"добавляется",
|
|
229
|
+
"написать",
|
|
230
|
+
"написал",
|
|
231
|
+
"написала",
|
|
232
|
+
"написало",
|
|
233
|
+
"напишу",
|
|
234
|
+
"написано",
|
|
235
|
+
"писать",
|
|
236
|
+
"пишу",
|
|
237
|
+
"пишем",
|
|
238
|
+
"пишет",
|
|
239
|
+
"написание",
|
|
240
|
+
"пишется",
|
|
241
|
+
"записать",
|
|
242
|
+
"записал",
|
|
243
|
+
"записала",
|
|
244
|
+
"записываю",
|
|
245
|
+
"записываем",
|
|
246
|
+
"записывает",
|
|
247
|
+
"записан",
|
|
248
|
+
"запись",
|
|
249
|
+
"записывается",
|
|
250
|
+
"прочитать",
|
|
251
|
+
"прочитал",
|
|
252
|
+
"прочитала",
|
|
253
|
+
"прочитало",
|
|
254
|
+
"прочитаю",
|
|
255
|
+
"прочитано",
|
|
256
|
+
"читать",
|
|
257
|
+
"читаю",
|
|
258
|
+
"читаем",
|
|
259
|
+
"читает",
|
|
260
|
+
"чтение",
|
|
261
|
+
"читается",
|
|
262
|
+
"проверить",
|
|
263
|
+
"проверил",
|
|
264
|
+
"проверила",
|
|
265
|
+
"проверило",
|
|
266
|
+
"проверю",
|
|
267
|
+
"проверено",
|
|
268
|
+
"проверка",
|
|
269
|
+
"проверяю",
|
|
270
|
+
"проверяем",
|
|
271
|
+
"проверяет",
|
|
272
|
+
"проверяется",
|
|
273
|
+
"открыть",
|
|
274
|
+
"открыл",
|
|
275
|
+
"открыла",
|
|
276
|
+
"открыло",
|
|
277
|
+
"открою",
|
|
278
|
+
"открыто",
|
|
279
|
+
"открытие",
|
|
280
|
+
"открываю",
|
|
281
|
+
"открываем",
|
|
282
|
+
"открывает",
|
|
283
|
+
"открывается",
|
|
284
|
+
"найти",
|
|
285
|
+
"нашел",
|
|
286
|
+
"нашла",
|
|
287
|
+
"нашло",
|
|
288
|
+
"найду",
|
|
289
|
+
"найдено",
|
|
290
|
+
"поиск",
|
|
291
|
+
"ищу",
|
|
292
|
+
"ищем",
|
|
293
|
+
"ищет",
|
|
294
|
+
"ищется",
|
|
295
|
+
"исправить",
|
|
296
|
+
"исправил",
|
|
297
|
+
"исправила",
|
|
298
|
+
"исправило",
|
|
299
|
+
"исправлю",
|
|
300
|
+
"исправлено",
|
|
301
|
+
"исправление",
|
|
302
|
+
"исправляю",
|
|
303
|
+
"исправляем",
|
|
304
|
+
"исправляет",
|
|
305
|
+
"исправляется",
|
|
306
|
+
"переместить",
|
|
307
|
+
"переместил",
|
|
308
|
+
"переместила",
|
|
309
|
+
"переместило",
|
|
310
|
+
"перемещу",
|
|
311
|
+
"перемещен",
|
|
312
|
+
"перемещение",
|
|
313
|
+
"перемещаю",
|
|
314
|
+
"перемещаем",
|
|
315
|
+
"перемещает",
|
|
316
|
+
"перемещается",
|
|
317
|
+
"переименовать",
|
|
318
|
+
"переименовал",
|
|
319
|
+
"переименовала",
|
|
320
|
+
"переименую",
|
|
321
|
+
"переименован",
|
|
322
|
+
"переименование",
|
|
323
|
+
"переименовываю",
|
|
324
|
+
"переименовываем",
|
|
325
|
+
"переименовывает",
|
|
326
|
+
"переименовывается",
|
|
327
|
+
"скопировать",
|
|
328
|
+
"скопировал",
|
|
329
|
+
"скопировала",
|
|
330
|
+
"скопировало",
|
|
331
|
+
"скопирую",
|
|
332
|
+
"скопирован",
|
|
333
|
+
"копирование",
|
|
334
|
+
"копирую",
|
|
335
|
+
"копируем",
|
|
336
|
+
"копирует",
|
|
337
|
+
"копируется",
|
|
338
|
+
"собрать",
|
|
339
|
+
"собрал",
|
|
340
|
+
"собрала",
|
|
341
|
+
"собран",
|
|
342
|
+
"сборка",
|
|
343
|
+
"собираю",
|
|
344
|
+
"собираем",
|
|
345
|
+
"собирает",
|
|
346
|
+
"собирается",
|
|
347
|
+
"запустить",
|
|
348
|
+
"запустил",
|
|
349
|
+
"запустила",
|
|
350
|
+
"запустило",
|
|
351
|
+
"запущен",
|
|
352
|
+
"запуск",
|
|
353
|
+
"запускаю",
|
|
354
|
+
"запускаем",
|
|
355
|
+
"запускает",
|
|
356
|
+
"запускается",
|
|
357
|
+
"тестировать",
|
|
358
|
+
"тестировал",
|
|
359
|
+
"тестировала",
|
|
360
|
+
"тестирую",
|
|
361
|
+
"тестируем",
|
|
362
|
+
"тестирует",
|
|
363
|
+
"тестирован",
|
|
364
|
+
"тестирование",
|
|
365
|
+
"тестируется",
|
|
366
|
+
"импортировать",
|
|
367
|
+
"импортировал",
|
|
368
|
+
"импортировала",
|
|
369
|
+
"импортирую",
|
|
370
|
+
"импортируем",
|
|
371
|
+
"импортирует",
|
|
372
|
+
"импортирован",
|
|
373
|
+
"импорт",
|
|
374
|
+
"импортируется",
|
|
375
|
+
"экспортировать",
|
|
376
|
+
"экспортировал",
|
|
377
|
+
"экспортировала",
|
|
378
|
+
"экспортирую",
|
|
379
|
+
"экспортируем",
|
|
380
|
+
"экспортирует",
|
|
381
|
+
"экспортирован",
|
|
382
|
+
"экспорт",
|
|
383
|
+
"экспортируется",
|
|
384
|
+
// Russian file nouns
|
|
385
|
+
"файл",
|
|
386
|
+
"файла",
|
|
387
|
+
"файле",
|
|
388
|
+
"файлы",
|
|
389
|
+
"файлов",
|
|
390
|
+
"файлам",
|
|
391
|
+
"файлами",
|
|
392
|
+
"путь",
|
|
393
|
+
"пути",
|
|
394
|
+
"путей",
|
|
395
|
+
"путем",
|
|
396
|
+
"путям",
|
|
397
|
+
"путями",
|
|
398
|
+
"директория",
|
|
399
|
+
"директории",
|
|
400
|
+
"директорию",
|
|
401
|
+
"директорий",
|
|
402
|
+
"директориях",
|
|
403
|
+
"директориями",
|
|
404
|
+
"папка",
|
|
405
|
+
"папке",
|
|
406
|
+
"папки",
|
|
407
|
+
"папок",
|
|
408
|
+
"папкам",
|
|
409
|
+
"папками",
|
|
410
|
+
"модуль",
|
|
411
|
+
"модуля",
|
|
412
|
+
"модуле",
|
|
413
|
+
"модули",
|
|
414
|
+
"модулей",
|
|
415
|
+
"модулям",
|
|
416
|
+
"модулями",
|
|
417
|
+
"скрипт",
|
|
418
|
+
"скрипта",
|
|
419
|
+
"скрипте",
|
|
420
|
+
"скрипты",
|
|
421
|
+
"скриптов",
|
|
422
|
+
"скриптам",
|
|
423
|
+
"скриптами",
|
|
424
|
+
"код",
|
|
425
|
+
"кода",
|
|
426
|
+
"коде",
|
|
427
|
+
"коды",
|
|
428
|
+
"кодов",
|
|
429
|
+
"кодам",
|
|
430
|
+
"кодами",
|
|
66
431
|
]);
|
|
67
432
|
export class FactualCheck {
|
|
68
433
|
knownPaths = new Set();
|
|
@@ -104,7 +469,7 @@ export class FactualCheck {
|
|
|
104
469
|
let match;
|
|
105
470
|
while ((match = pathPatterns.exec(content)) !== null) {
|
|
106
471
|
const file = match[1];
|
|
107
|
-
if (file.includes(
|
|
472
|
+
if (file.includes("/") || file.includes("\\")) {
|
|
108
473
|
this.knownPaths.add(file);
|
|
109
474
|
}
|
|
110
475
|
const base = file.split(/[/\\]/).pop();
|
|
@@ -116,7 +481,9 @@ export class FactualCheck {
|
|
|
116
481
|
try {
|
|
117
482
|
// Skip absolute paths — they're either system paths or outside the project.
|
|
118
483
|
// On Windows, existsSync('/...') can hang on certain paths.
|
|
119
|
-
if (path.startsWith("/") ||
|
|
484
|
+
if (path.startsWith("/") ||
|
|
485
|
+
path.startsWith("~") ||
|
|
486
|
+
/^[A-Za-z]:/.test(path)) {
|
|
120
487
|
return false;
|
|
121
488
|
}
|
|
122
489
|
return existsSync(join(this.baseDir, path));
|
|
@@ -125,6 +492,28 @@ export class FactualCheck {
|
|
|
125
492
|
return false;
|
|
126
493
|
}
|
|
127
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Check whether a matched path-like string appears in a context that suggests
|
|
497
|
+
* the model is actually talking about a file operation, rather than just
|
|
498
|
+
* mentioning a technology or framework name (e.g. "React/Next.js").
|
|
499
|
+
*
|
|
500
|
+
* Looks at the 8 words immediately preceding the match.
|
|
501
|
+
*/
|
|
502
|
+
hasFileOperationContext(text, match) {
|
|
503
|
+
const escaped = match.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
504
|
+
const regex = new RegExp(escaped, "gi");
|
|
505
|
+
let result;
|
|
506
|
+
while ((result = regex.exec(text)) !== null) {
|
|
507
|
+
const start = result.index;
|
|
508
|
+
const before = text.slice(Math.max(0, start - 100), start);
|
|
509
|
+
const words = [...before.matchAll(/[a-zA-Zа-яА-ЯёЁ0-9]+/g)].map((m) => m[0].toLowerCase());
|
|
510
|
+
const lastWords = words.slice(-8);
|
|
511
|
+
if (lastWords.some((w) => FILE_OPERATION_CONTEXT.has(w))) {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
128
517
|
validate(response) {
|
|
129
518
|
const pathRegex = /[\w\-./]+\.\w+/g;
|
|
130
519
|
const mentionedPaths = response.match(pathRegex) || [];
|
|
@@ -154,6 +543,8 @@ export class FactualCheck {
|
|
|
154
543
|
if (urlPattern.test(response))
|
|
155
544
|
return false;
|
|
156
545
|
}
|
|
546
|
+
if (!this.hasFileOperationContext(response, p))
|
|
547
|
+
return false;
|
|
157
548
|
return true;
|
|
158
549
|
});
|
|
159
550
|
if (unknownPaths.length > 0) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { HallucinationDetector } from
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export { ConfidenceCheck } from './confidence';
|
|
1
|
+
export { HallucinationDetector, } from "./detector";
|
|
2
|
+
export { ConsistencyCheck } from "./consistency";
|
|
3
|
+
export { ConfidenceCheck } from "./confidence";
|
package/dist/tools/create-dir.js
CHANGED
|
@@ -50,7 +50,6 @@ export const createDirTool = {
|
|
|
50
50
|
ctx.fileOperationsCount = currentCount + 1;
|
|
51
51
|
// Log directory creation
|
|
52
52
|
logFileWrite(ctx.sessionId, path, true, "Directory created");
|
|
53
|
-
ctx.trackCreatedPath?.(path);
|
|
54
53
|
return { success: true, output: t("file.created", { path }) };
|
|
55
54
|
},
|
|
56
55
|
};
|
|
@@ -57,7 +57,6 @@ export const deleteFileTool = {
|
|
|
57
57
|
ctx.fileOperationsCount = currentCount + 1;
|
|
58
58
|
// Log successful file deletion
|
|
59
59
|
logFileDelete(ctx.sessionId, path, true);
|
|
60
|
-
ctx.trackDeletedPath?.(path);
|
|
61
60
|
return { success: true, output: t("file.deleted", { path }), diff };
|
|
62
61
|
},
|
|
63
62
|
};
|
package/dist/tools/read-file.js
CHANGED
|
@@ -44,10 +44,7 @@ export const readFileTool = {
|
|
|
44
44
|
if (!existsSync(resolved)) {
|
|
45
45
|
return { success: false, output: t("file.notfound", { path }) };
|
|
46
46
|
}
|
|
47
|
-
ctx.trackReadPath?.(path);
|
|
48
47
|
const content = readFileSync(resolved, "utf-8");
|
|
49
|
-
// Track file paths mentioned in the document (e.g. structure.md, README)
|
|
50
|
-
ctx.trackDocumentContent?.(content);
|
|
51
48
|
const lines = content.split("\n");
|
|
52
49
|
const total = lines.length;
|
|
53
50
|
const offset = args.offset || 1;
|
package/dist/tools/write-file.js
CHANGED
|
@@ -73,8 +73,7 @@ export const writeFileTool = {
|
|
|
73
73
|
// Increment file operations counter
|
|
74
74
|
ctx.fileOperationsCount = currentCount + 1;
|
|
75
75
|
// Log successful file write
|
|
76
|
-
logFileWrite(ctx.sessionId, path, true, `File ${fileExists ?
|
|
77
|
-
ctx.trackCreatedPath?.(path);
|
|
76
|
+
logFileWrite(ctx.sessionId, path, true, `File ${fileExists ? "updated" : "created"}`);
|
|
78
77
|
return { success: true, output: t("file.written", { path }), diff };
|
|
79
78
|
},
|
|
80
79
|
};
|