micro-models-agent 2.11.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (347) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/js/tabs.js +22 -3
  3. package/dist/cli/cache-line.js +1 -0
  4. package/dist/cli/command-suggest.js +5 -4
  5. package/dist/cli/commands.js +231 -122
  6. package/dist/cli/completer.js +132 -31
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/json-payload.js +4 -4
  9. package/dist/cli/main.js +166 -61
  10. package/dist/cli/onboarding-commands.js +126 -0
  11. package/dist/cli/plugin-commands.js +2 -1
  12. package/dist/cli/relaunch.js +16 -13
  13. package/dist/cli/repl-commands-lsp.js +75 -0
  14. package/dist/cli/repl-commands-skills.js +105 -0
  15. package/dist/cli/repl-commands.js +589 -266
  16. package/dist/cli/repl.js +436 -177
  17. package/dist/cli/run-result.js +5 -6
  18. package/dist/cli/security-commands.js +37 -28
  19. package/dist/cli/setup-order.js +4 -55
  20. package/dist/cli/setup-prompt.js +3 -2
  21. package/dist/cli/setup.js +27 -107
  22. package/dist/cli/web-command.js +98 -0
  23. package/dist/config/budget.js +5 -5
  24. package/dist/config/config.js +103 -96
  25. package/dist/config/defaults.js +38 -8
  26. package/dist/config/domains.js +24 -27
  27. package/dist/config/env.js +46 -0
  28. package/dist/config/experts.js +2 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/paths.js +56 -0
  31. package/dist/config/redact.js +44 -0
  32. package/dist/config/registry-extra.js +334 -0
  33. package/dist/config/registry.js +584 -0
  34. package/dist/config/security.js +35 -36
  35. package/dist/config/timeouts.js +74 -0
  36. package/dist/config/utils.js +16 -15
  37. package/dist/core/agent/artifact-progress.js +59 -0
  38. package/dist/core/agent/artifact-resolve.js +79 -0
  39. package/dist/core/agent/audit-gate.js +7 -3
  40. package/dist/core/agent/compaction.js +2 -2
  41. package/dist/core/agent/constants.js +33 -4
  42. package/dist/core/agent/context-log.js +454 -0
  43. package/dist/core/agent/context-renderer.js +5 -0
  44. package/dist/core/agent/critic-gate.js +132 -0
  45. package/dist/core/agent/hallucination-gate.js +148 -38
  46. package/dist/core/agent/loop-state.js +15 -0
  47. package/dist/core/agent/prefix-monitor.js +1 -1
  48. package/dist/core/agent/reasoning-resolver.js +10 -1
  49. package/dist/core/agent/token-tracker.js +7 -4
  50. package/dist/core/agent/tool-batch.js +93 -18
  51. package/dist/core/agent/tool-images.js +18 -0
  52. package/dist/core/agent/tool-output.js +65 -11
  53. package/dist/core/agent/tool-pairing.js +60 -0
  54. package/dist/core/agent-moe.js +25 -24
  55. package/dist/core/agent.js +429 -108
  56. package/dist/core/bootstrap/agent-phase.js +66 -0
  57. package/dist/core/bootstrap/config-phase.js +86 -0
  58. package/dist/core/bootstrap/modules-phase.js +269 -0
  59. package/dist/core/bootstrap/provider-phase.js +183 -0
  60. package/dist/core/bootstrap/session-phase.js +204 -0
  61. package/dist/core/bootstrap/state.js +94 -0
  62. package/dist/core/bootstrap/workspace-phase.js +115 -0
  63. package/dist/core/bootstrap.js +92 -660
  64. package/dist/core/crash-handler.js +17 -7
  65. package/dist/core/environment.js +37 -12
  66. package/dist/core/host-plan.js +43 -0
  67. package/dist/core/host.js +23 -0
  68. package/dist/core/index.js +1 -0
  69. package/dist/core/prompt-builder.js +17 -1
  70. package/dist/core/prompt-overflow.js +42 -43
  71. package/dist/core/session-logger.js +122 -30
  72. package/dist/core/system-prompt.js +117 -0
  73. package/dist/core/version.js +6 -4
  74. package/dist/core/web-host.js +34 -0
  75. package/dist/core/workspace.js +19 -15
  76. package/dist/i18n/index.js +37 -3
  77. package/dist/index.js +7 -3
  78. package/dist/llm/image-utils.js +55 -64
  79. package/dist/llm/llm-errors.js +9 -0
  80. package/dist/llm/model-loader.js +10 -32
  81. package/dist/llm/models-dev.js +121 -0
  82. package/dist/llm/openai-compat.js +146 -73
  83. package/dist/llm/orchestrator.js +33 -11
  84. package/dist/llm/provider-budget.js +2 -2
  85. package/dist/llm/provider-context.js +233 -0
  86. package/dist/llm/provider.js +1 -1
  87. package/dist/llm/response.js +5 -0
  88. package/dist/llm/token-counter.js +27 -11
  89. package/dist/logger/app-logger.js +48 -22
  90. package/dist/logger/file-log.js +28 -19
  91. package/dist/logger/index.js +1 -0
  92. package/dist/main.js +273 -47
  93. package/dist/migration/backup.js +14 -3
  94. package/dist/migration/detect.js +12 -3
  95. package/dist/migration/index.js +1 -0
  96. package/dist/modules/artifacts/store.js +9 -8
  97. package/dist/modules/browser/actions.js +6 -0
  98. package/dist/modules/browser/bridge-client.js +14 -12
  99. package/dist/modules/browser/bridge-path.js +4 -4
  100. package/dist/modules/browser/console-buffer.js +37 -0
  101. package/dist/modules/browser/cookie-store.js +5 -0
  102. package/dist/modules/browser/driver-types.js +1 -0
  103. package/dist/modules/browser/driver.js +19 -11
  104. package/dist/modules/browser/index.js +3 -1
  105. package/dist/modules/browser/module.js +2 -1
  106. package/dist/modules/browser/session.js +10 -30
  107. package/dist/modules/browser/snapshot.js +19 -13
  108. package/dist/modules/browser/types.js +1 -0
  109. package/dist/modules/certification/cli.js +28 -20
  110. package/dist/modules/certification/fact-checker.js +9 -1
  111. package/dist/modules/certification/loader.js +9 -2
  112. package/dist/modules/certification/manifest.js +83 -21
  113. package/dist/modules/certification/runner.js +23 -13
  114. package/dist/modules/certification/scenarios.js +20 -20
  115. package/dist/modules/certification/syntax-scenarios.js +1 -2
  116. package/dist/modules/context/chunk-query.js +11 -9
  117. package/dist/modules/context/fact-extractor.js +37 -39
  118. package/dist/modules/context/index.js +1 -0
  119. package/dist/modules/context/manager.js +106 -80
  120. package/dist/modules/critic/client.js +176 -0
  121. package/dist/modules/critic/evidence.js +81 -0
  122. package/dist/modules/critic/provider.js +45 -0
  123. package/dist/modules/critic/tools.js +78 -0
  124. package/dist/modules/critic/types.js +1 -0
  125. package/dist/modules/execution/audit-runners.js +28 -34
  126. package/dist/modules/execution/auditor.js +53 -49
  127. package/dist/modules/execution/execution-plugin.js +142 -102
  128. package/dist/modules/execution/index.js +1 -1
  129. package/dist/modules/execution/input-from.js +37 -13
  130. package/dist/modules/execution/module.js +368 -191
  131. package/dist/modules/execution/moe-executor.js +53 -26
  132. package/dist/modules/execution/plan-actions.js +320 -202
  133. package/dist/modules/execution/plan-checks.js +35 -0
  134. package/dist/modules/execution/plan-coverage.js +36 -33
  135. package/dist/modules/execution/plan-steps.js +11 -3
  136. package/dist/modules/execution/plan-store.js +83 -17
  137. package/dist/modules/execution/plan-tool.js +89 -35
  138. package/dist/modules/execution/plan-validator.js +30 -10
  139. package/dist/modules/execution/planner.js +22 -13
  140. package/dist/modules/execution/stuck-detector.js +196 -93
  141. package/dist/modules/execution/tracker.js +22 -8
  142. package/dist/modules/execution/verifier.js +69 -48
  143. package/dist/modules/execution/windows-commands.js +12 -12
  144. package/dist/modules/hallucination/artifact-claim.js +93 -0
  145. package/dist/modules/hallucination/confidence.js +13 -11
  146. package/dist/modules/hallucination/consistency.js +10 -2
  147. package/dist/modules/hallucination/detector.js +10 -5
  148. package/dist/modules/hallucination/factual.js +43 -26
  149. package/dist/modules/hallucination/index.js +1 -0
  150. package/dist/modules/hallucination/js-identifiers.js +44 -45
  151. package/dist/modules/hallucination/llm-judge.js +17 -12
  152. package/dist/modules/hallucination/success-claim.js +59 -0
  153. package/dist/modules/indexer/cache.js +52 -4
  154. package/dist/modules/indexer/index.js +1 -0
  155. package/dist/modules/indexer/map-command.js +2 -4
  156. package/dist/modules/indexer/map-select.js +16 -16
  157. package/dist/modules/indexer/module.js +47 -12
  158. package/dist/modules/indexer/project-profile.js +8 -7
  159. package/dist/modules/indexer/symbols.js +15 -15
  160. package/dist/modules/indexer/walker.js +159 -29
  161. package/dist/modules/lsp/check-tool.js +7 -5
  162. package/dist/modules/lsp/client.js +39 -39
  163. package/dist/modules/lsp/command.js +17 -24
  164. package/dist/modules/lsp/config.js +12 -11
  165. package/dist/modules/lsp/index.js +1 -0
  166. package/dist/modules/lsp/module.js +48 -46
  167. package/dist/modules/lsp/probe.js +13 -14
  168. package/dist/modules/lsp/project-root.js +14 -17
  169. package/dist/modules/lsp/startup-check.js +18 -16
  170. package/dist/modules/mcp/client.js +59 -62
  171. package/dist/modules/mcp/index.js +1 -0
  172. package/dist/modules/mcp/module.js +66 -37
  173. package/dist/modules/mcp/registry.js +5 -0
  174. package/dist/modules/memory/index.js +1 -0
  175. package/dist/modules/memory/module.js +12 -8
  176. package/dist/modules/memory/search.js +4 -1
  177. package/dist/modules/memory/store.js +40 -2
  178. package/dist/modules/onboarding/init.js +47 -0
  179. package/dist/modules/onboarding/meet.js +30 -0
  180. package/dist/modules/onboarding/mode.js +30 -0
  181. package/dist/modules/onboarding/prompts.js +43 -0
  182. package/dist/modules/pipelines/engine.js +10 -0
  183. package/dist/modules/pipelines/index.js +1 -0
  184. package/dist/modules/pipelines/parser.js +4 -1
  185. package/dist/modules/pipelines/template.js +4 -0
  186. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  187. package/dist/modules/plugins/builtin/notify.js +2 -0
  188. package/dist/modules/plugins/index.js +1 -0
  189. package/dist/modules/plugins/loader.js +18 -3
  190. package/dist/modules/plugins/manager.js +37 -17
  191. package/dist/modules/pricing/index.js +10 -11
  192. package/dist/modules/pricing/prices.js +12 -12
  193. package/dist/modules/processes/index.js +1 -0
  194. package/dist/modules/processes/kill-tree.js +27 -31
  195. package/dist/modules/processes/registry.js +44 -25
  196. package/dist/modules/processes/runner.js +4 -4
  197. package/dist/modules/providers/capabilities.js +28 -0
  198. package/dist/modules/providers/create.js +3 -4
  199. package/dist/modules/providers/factory.js +7 -7
  200. package/dist/modules/providers/fallback.js +12 -12
  201. package/dist/modules/providers/health.js +4 -4
  202. package/dist/modules/providers/manager.js +50 -19
  203. package/dist/modules/providers/naming.js +15 -0
  204. package/dist/modules/providers/preflight.js +71 -0
  205. package/dist/modules/providers/presets.js +39 -8
  206. package/dist/modules/providers/registry.js +7 -2
  207. package/dist/modules/reasoning/policy.js +8 -6
  208. package/dist/modules/reasoning/probe.js +30 -25
  209. package/dist/modules/security/audit-log.js +19 -36
  210. package/dist/modules/security/audit-notifier.js +35 -75
  211. package/dist/modules/security/command-validator.js +99 -51
  212. package/dist/modules/security/content-scanner.js +8 -8
  213. package/dist/modules/security/data-sanitizer.js +9 -16
  214. package/dist/modules/security/encryption.js +32 -52
  215. package/dist/modules/security/index.js +2 -1
  216. package/dist/modules/security/network-validator.js +120 -43
  217. package/dist/modules/security/path-validator.js +61 -46
  218. package/dist/modules/security/protected-paths.js +73 -0
  219. package/dist/modules/security/rate-limiter.js +17 -49
  220. package/dist/modules/security/security-policies.js +25 -57
  221. package/dist/modules/security/session-encryption.js +32 -71
  222. package/dist/modules/security/session-isolation.js +15 -28
  223. package/dist/modules/session/auto-name.js +63 -0
  224. package/dist/modules/session/export.js +98 -0
  225. package/dist/modules/session/index.js +1 -0
  226. package/dist/modules/session/manager.js +79 -28
  227. package/dist/modules/session/module.js +4 -3
  228. package/dist/modules/session/store.js +57 -20
  229. package/dist/modules/setup/answers.js +35 -0
  230. package/dist/modules/setup/apply.js +79 -0
  231. package/dist/modules/setup/discovery.js +72 -0
  232. package/dist/modules/skills/index.js +1 -0
  233. package/dist/modules/skills/loader.js +9 -1
  234. package/dist/modules/skills/module.js +11 -0
  235. package/dist/modules/updater/changelog-reader.js +15 -16
  236. package/dist/modules/updater/checker.js +9 -4
  237. package/dist/modules/updater/dev-detect.js +7 -6
  238. package/dist/modules/updater/index.js +1 -0
  239. package/dist/modules/updater/module.js +17 -15
  240. package/dist/modules/user-profile/compressor.js +2 -0
  241. package/dist/modules/user-profile/index.js +1 -0
  242. package/dist/modules/user-profile/profile.js +10 -1
  243. package/dist/modules/webui/answer-api.js +29 -0
  244. package/dist/modules/webui/assets.js +50 -0
  245. package/dist/modules/webui/body.js +21 -0
  246. package/dist/modules/webui/commands-settings.js +209 -0
  247. package/dist/modules/webui/commands-shared.js +60 -0
  248. package/dist/modules/webui/commands.js +174 -0
  249. package/dist/modules/webui/config-api.js +134 -0
  250. package/dist/modules/webui/file-api.js +202 -0
  251. package/dist/modules/webui/forward.js +159 -0
  252. package/dist/modules/webui/history-api.js +112 -0
  253. package/dist/modules/webui/host-state.js +60 -0
  254. package/dist/modules/webui/i18n-api.js +9 -0
  255. package/dist/modules/webui/limits.js +6 -0
  256. package/dist/modules/webui/log-forward.js +20 -0
  257. package/dist/modules/webui/memory-commands.js +64 -0
  258. package/dist/modules/webui/model-cache.js +18 -0
  259. package/dist/modules/webui/module.js +148 -0
  260. package/dist/modules/webui/persist.js +24 -0
  261. package/dist/modules/webui/prompt-io.js +97 -0
  262. package/dist/modules/webui/providers-api.js +337 -0
  263. package/dist/modules/webui/providers-view.js +56 -0
  264. package/dist/modules/webui/reasoning-view.js +15 -0
  265. package/dist/modules/webui/run-agent.js +81 -0
  266. package/dist/modules/webui/server.js +284 -0
  267. package/dist/modules/webui/sessions-api.js +178 -0
  268. package/dist/modules/webui/settings-api.js +561 -0
  269. package/dist/modules/webui/setup-api.js +201 -0
  270. package/dist/modules/webui/state.js +74 -0
  271. package/dist/modules/webui/util.js +261 -0
  272. package/dist/output/bus.js +7 -2
  273. package/dist/output/channel.js +80 -28
  274. package/dist/output/format.js +2 -1
  275. package/dist/output/index.js +1 -0
  276. package/dist/output/json-sink.js +5 -3
  277. package/dist/output/machine.js +4 -3
  278. package/dist/output/session-sink.js +4 -4
  279. package/dist/tools/approve.js +4 -0
  280. package/dist/tools/attach-image.js +5 -4
  281. package/dist/tools/auto-fixer.js +26 -44
  282. package/dist/tools/auto-tags.js +83 -0
  283. package/dist/tools/bash.js +193 -116
  284. package/dist/tools/browser.js +76 -31
  285. package/dist/tools/chunk-query.js +14 -6
  286. package/dist/tools/create-dir.js +9 -5
  287. package/dist/tools/delete-file.js +9 -5
  288. package/dist/tools/download-file.js +32 -7
  289. package/dist/tools/edit-file.js +52 -14
  290. package/dist/tools/enable-tools.js +6 -6
  291. package/dist/tools/executor.js +132 -27
  292. package/dist/tools/file-info.js +3 -1
  293. package/dist/tools/filter-tools.js +3 -3
  294. package/dist/tools/glob-tool.js +4 -1
  295. package/dist/tools/grep-tool.js +13 -10
  296. package/dist/tools/hidden-tools-block.js +6 -6
  297. package/dist/tools/index.js +16 -8
  298. package/dist/tools/list-dir.js +4 -2
  299. package/dist/tools/load-skill.js +7 -0
  300. package/dist/tools/mcp-call.js +15 -4
  301. package/dist/tools/meet-done.js +14 -0
  302. package/dist/tools/move-file.js +14 -7
  303. package/dist/tools/path-utils.js +29 -29
  304. package/dist/tools/pipeline-run.js +29 -7
  305. package/dist/tools/preview.js +1 -1
  306. package/dist/tools/process-ids.js +31 -0
  307. package/dist/tools/process-kill.js +9 -16
  308. package/dist/tools/process-list.js +1 -0
  309. package/dist/tools/process-log.js +11 -16
  310. package/dist/tools/process-wait.js +63 -0
  311. package/dist/tools/prompt-io.js +4 -3
  312. package/dist/tools/question.js +172 -25
  313. package/dist/tools/read-file.js +55 -34
  314. package/dist/tools/recall.js +14 -8
  315. package/dist/tools/registry.js +6 -5
  316. package/dist/tools/remember.js +8 -4
  317. package/dist/tools/scope-request.js +11 -6
  318. package/dist/tools/search-history.js +5 -2
  319. package/dist/tools/session-info.js +2 -0
  320. package/dist/tools/set-thinking.js +8 -4
  321. package/dist/tools/subagent.js +59 -39
  322. package/dist/tools/syntax-validator.js +292 -90
  323. package/dist/tools/user-input.js +22 -18
  324. package/dist/tools/web-browse.js +9 -3
  325. package/dist/tools/web-fetch.js +12 -23
  326. package/dist/tools/web-search.js +7 -5
  327. package/dist/tools/write-file.js +45 -15
  328. package/dist/ui/action-log.js +13 -0
  329. package/dist/ui/box.js +4 -3
  330. package/dist/ui/colors.js +2 -0
  331. package/dist/ui/diff.js +6 -5
  332. package/dist/ui/index.js +1 -0
  333. package/dist/ui/line-editor.js +230 -122
  334. package/dist/ui/line-math.js +10 -2
  335. package/dist/ui/md-formatter.js +57 -37
  336. package/dist/ui/output.js +8 -3
  337. package/dist/ui/plan-view.js +21 -10
  338. package/dist/ui/renderer.js +393 -67
  339. package/dist/ui/spinner.js +16 -5
  340. package/dist/ui/table.js +15 -17
  341. package/dist/ui/theme.js +143 -0
  342. package/dist/utils/child-run.js +20 -0
  343. package/dist/utils/error.js +1 -1
  344. package/dist/utils/index.js +2 -0
  345. package/dist/utils/retry.js +2 -2
  346. package/dist/utils/truncate.js +1 -1
  347. package/package.json +1 -1
@@ -1,8 +1,10 @@
1
+ // Фактическая проверка: упомянутые в ответе пути файлов против диска и индекса.
1
2
  import { existsSync, readdirSync } from "fs";
2
3
  import { resolve, isAbsolute, join } from "path";
3
4
  import { t } from "../../i18n/index";
4
5
  import { extractFileLikeTokens, stripUrls } from "./js-identifiers";
5
- /** Generated/cached dirs to skip when scanning for bare file names. */
6
+ import { HALLUCINATION_NAME_CACHE_TTL_MS, HALLUCINATION_RESCAN_COOLDOWN_MS, } from "../../config/timeouts";
7
+ /** Генерированные/кэшированные каталоги — пропускаем при поиске голых имён. */
6
8
  const IGNORED_DIRS = new Set([
7
9
  "node_modules",
8
10
  ".git",
@@ -12,26 +14,30 @@ const IGNORED_DIRS = new Set([
12
14
  ".mma",
13
15
  "_testing",
14
16
  ]);
17
+ /** Проверка фактов: упомянутые файлы существуют на диске/в индексе, иначе warn. */
15
18
  export class FactualCheck {
16
19
  baseDir;
17
20
  nameCache = null;
18
21
  nameCacheTime = 0;
19
22
  lastRescan = 0;
20
- static NAME_CACHE_TTL_MS = 5_000;
21
- static RESCAN_COOLDOWN_MS = 2_000;
23
+ static NAME_CACHE_TTL_MS = HALLUCINATION_NAME_CACHE_TTL_MS;
24
+ static RESCAN_COOLDOWN_MS = HALLUCINATION_RESCAN_COOLDOWN_MS;
22
25
  static MAX_INDEXED_FILES = 20_000;
23
- /** Files known from compaction summaries — survive context resets. */
26
+ /** Файлы, известные из сводок компакции — переживают сбросы контекста. */
24
27
  knownFiles = new Set();
25
28
  constructor(baseDir) {
26
29
  this.baseDir = baseDir;
27
30
  }
28
- /** Add file paths known from compaction summaries. These are checked
29
- * before the filesystem scan so bare filenames from compacted turns
30
- * are recognized even when the directory is in IGNORED_DIRS. */
31
+ /** Добавляет пути, известные из сводок компакции. Проверяются раньше скана ФС:
32
+ * голые имена из уплотнённых ходов узнаются, даже если каталог в IGNORED_DIRS. */
31
33
  addKnownFiles(files) {
32
34
  for (const f of files)
33
35
  this.knownFiles.add(f);
34
36
  }
37
+ /**
38
+ * Валидирует пути файлов в ответе: warn со списком отсутствующих, иначе pass.
39
+ * @param response текст ответа модели; @param deletedFiles уже удалённые (не флагуются).
40
+ */
35
41
  validate(response, deletedFiles) {
36
42
  const filePaths = this.extractFilePaths(response);
37
43
  if (filePaths.length === 0)
@@ -58,13 +64,13 @@ export class FactualCheck {
58
64
  return { status: "pass" };
59
65
  }
60
66
  /**
61
- * Bare filenames ("bikes.json") and relative paths ("src/data/bikes.json")
62
- * both land on the project tree. Absolute paths are checked as-is.
67
+ * Голые имена ("bikes.json") и относительные пути ("src/data/bikes.json")
68
+ * садятся на дерево проекта. Абсолютные проверяются как есть.
63
69
  */
64
70
  pathExists(fp) {
65
71
  if (isAbsolute(fp))
66
72
  return existsSync(fp);
67
- // Check known files from compaction summaries first
73
+ // Сначала известные файлы из сводок компакции
68
74
  if (this.knownFiles.has(fp))
69
75
  return true;
70
76
  for (const cand of this.dotfileVariants(fp)) {
@@ -73,33 +79,32 @@ export class FactualCheck {
73
79
  if (existsSync(resolve(this.baseDir, cand)))
74
80
  return true;
75
81
  }
76
- // "foo.json" has no directory part — treat it as a bare name that may
77
- // live anywhere under baseDir (e.g. src/data/bikes.json). Resolving it
78
- // against the workspace root alone produced false "unknown file" warnings.
82
+ // "foo.json" без каталога — голое имя, которое может жить где угодно под
83
+ // baseDir (напр. src/data/bikes.json). Резолв только от корня давал ложные
84
+ // "unknown file".
79
85
  if (!fp.includes("/") && !fp.includes("\\")) {
80
86
  return this.bareNameExists(fp);
81
87
  }
82
88
  return false;
83
89
  }
84
90
  /**
85
- * The token regex cannot anchor a leading dot (`\b` never matches between a
86
- * non-word char and `.`), so dotfiles (.prettierrc.json, .env, …) are
87
- * extracted as "prettierrc.json". Try the dot-prefixed basename too before
88
- * declaring the file missing. "src/config.json" also yields
89
- * "src/.config.json". For paths like "mma/index-cache.json", also try
90
- * ".mma/index-cache.json" (dot-prefixed parent directories).
91
+ * Токен-regex не якорит ведущую точку (`\b` не матчится между не-словом и
92
+ * `.`), поэтому дотфайлы (.prettierrc.json, .env, …) извлекаются как
93
+ * "prettierrc.json". Перед вердиктом "нет файла" пробуем basename с точкой,
94
+ * "src/config.json" даёт и "src/.config.json", а "mma/index-cache.json" —
95
+ * и ".mma/index-cache.json" (родители с точкой).
91
96
  */
92
97
  dotfileVariants(fp) {
93
98
  const variants = [fp];
94
99
  const sep = fp.includes("\\") ? "\\" : "/";
95
100
  const parts = fp.split(sep);
96
- // Try dot-prefixing the basename (existing logic)
101
+ // Пробуем basename с точкой (старая логика)
97
102
  const base = parts[parts.length - 1];
98
103
  if (!base.startsWith(".")) {
99
104
  const withDotBase = [...parts.slice(0, -1), `.${base}`].join(sep);
100
105
  variants.push(withDotBase);
101
106
  }
102
- // Try dot-prefixing each parent directory component
107
+ // Пробуем точку на каждом родительском компоненте
103
108
  for (let i = 0; i < parts.length - 1; i++) {
104
109
  if (!parts[i].startsWith(".")) {
105
110
  const dotted = [...parts.slice(0, i), `.${parts[i]}`, ...parts.slice(i + 1)].join(sep);
@@ -115,15 +120,27 @@ export class FactualCheck {
115
120
  return true;
116
121
  if (this.indexHas(cand))
117
122
  return true;
118
- // Check known files — a bare name may match a full path from compaction
123
+ // Известные файлы: голое имя может совпасть с полным путём из компакции
119
124
  for (const known of this.knownFiles) {
120
125
  if (known.endsWith(`/${cand}`) || known.endsWith(`\\${cand}`) || known === cand) {
121
126
  return true;
122
127
  }
123
128
  }
124
129
  }
125
- // A write_file from this session may have created the file after the
126
- // index was built. Do a one-off rescan (rate-limited) before warning.
130
+ // Имя файла может содержать пробел: «отчёт 1.txt» извлекается токеном
131
+ // «1.txt». Прежде чем объявлять файл отсутствующим, проверяем, не
132
+ // заканчивается ли какое-то известное имя на « 1.txt».
133
+ const spaceName = ` ${name}`;
134
+ for (const known of this.knownFiles) {
135
+ if (known.endsWith(spaceName))
136
+ return true;
137
+ }
138
+ for (const key of this.getNameIndex().keys()) {
139
+ if (key.endsWith(spaceName))
140
+ return true;
141
+ }
142
+ // write_file текущей сессии мог создать файл после сборки индекса.
143
+ // Перед warn — разовый рескан (rate-limited).
127
144
  const now = Date.now();
128
145
  if (now - this.lastRescan < FactualCheck.RESCAN_COOLDOWN_MS)
129
146
  return false;
@@ -136,8 +153,8 @@ export class FactualCheck {
136
153
  return (this.getNameIndex().get(name)?.length ?? 0) > 0;
137
154
  }
138
155
  /**
139
- * Basename → paths index, rebuilt every NAME_CACHE_TTL_MS so newly created
140
- * files (write_file) are picked up without a full scan on every response.
156
+ * Индекс basename → пути, пересборка каждые NAME_CACHE_TTL_MS, чтобы новые
157
+ * файлы (write_file) подхватывались без полного скана на каждый ответ.
141
158
  */
142
159
  getNameIndex() {
143
160
  const now = Date.now();
@@ -1,3 +1,4 @@
1
+ // Детектор галлюцинаций: реэкспорт проверок (консистентность, уверенность, факты, LLM-судья).
1
2
  export { HallucinationDetector } from "./detector";
2
3
  export { ConsistencyCheck } from "./consistency";
3
4
  export { ConfidenceCheck } from "./confidence";
@@ -1,19 +1,19 @@
1
1
  /**
2
- * JS/TS global identifiers whose member access (`process.env`, `console.log`,
3
- * `Math.max`) looks like a file path to `\b[\w./\\-]+\.[a-z]+\b`-style regexes
4
- * but is code, not a file. Shared by the hallucination factual check and the
5
- * execution auditor so both stop flagging member access as "missing files".
2
+ * JS/TS-глобалы, чей member access (`process.env`, `console.log`, `Math.max`)
3
+ * похож на путь к файлу для `\b[\w./\\-]+\.[a-z]+\b`-regex'ов, но является
4
+ * кодом, а не файлом. Общий для фактической проверки галлюцинаций и аудитора
5
+ * исполнения — оба перестают флаговать member access как "потерянные файлы".
6
6
  *
7
- * Also home of the shared "is this token actually a file?" filter
8
- * (`isFilePathLike` / `extractFileLikeTokens` / `stripUrls`) so the two
9
- * extractors cannot drift apart again (observed: auditor treated a URL
10
- * hostname `whom-pointing-jewel-maine.trycloudflare.com` in a plan step as a
11
- * missing file while the factual check correctly ignored it).
7
+ * Здесь же общий фильтр "этот токен вообще файл?"
8
+ * (`isFilePathLike` / `extractFileLikeTokens` / `stripUrls`), чтобы два
9
+ * экстрактора не расходились (было: аудитор считал hostname URL
10
+ * `whom-pointing-jewel-maine.trycloudflare.com` в шаге плана потерянным
11
+ * файлом, а фактическая проверка корректно игнорировала).
12
12
  *
13
- * Bare `word.ext` tokens are gated on an allow-list of real file extensions
14
- * (`FILE_EXTENSIONS`) instead of a deny-list of TLDs — a domain in any present
15
- * or future TLD (`Gismeteo.by`, `weather.com`, `example.xyz`) can never be
16
- * mistaken for a file, and no TLD list needs maintaining.
13
+ * Голые токены `word.ext` гейтятся allow-list'ом реальных расширений
14
+ * (`FILE_EXTENSIONS`), а не deny-list'ом TLD — домен в любом нынешнем или
15
+ * будущем TLD (`Gismeteo.by`, `weather.com`, `example.xyz`) никогда не
16
+ * спутается с файлом, и список TLD вести не нужно.
17
17
  */
18
18
  const JS_GLOBALS = new Set([
19
19
  "process",
@@ -72,9 +72,9 @@ const JS_GLOBALS = new Set([
72
72
  "node",
73
73
  ]);
74
74
  /**
75
- * True when `candidate` starts with a known JS/TS global followed by a dot
76
- * (`process.env`, `console.log`, `process.env.NODE` partial matches from
77
- * `\b[\w./\\-]+\.[a-z]+`-style regexes) — member access, not a file path.
75
+ * True, если `candidate` начинается с известного JS/TS-глобала и точки
76
+ * (`process.env`, `console.log`, частичные совпадения `process.env.NODE`
77
+ * от `\b[\w./\\-]+\.[a-z]+`-regex'ов) — member access, не путь к файлу.
78
78
  */
79
79
  export function isJsMemberAccess(candidate) {
80
80
  const lower = candidate.toLowerCase();
@@ -82,16 +82,16 @@ export function isJsMemberAccess(candidate) {
82
82
  return m !== null && JS_GLOBALS.has(m[1]);
83
83
  }
84
84
  /**
85
- * File extensions that make a bare `word.ext` token a plausible file path.
85
+ * Расширения, делающие голый токен `word.ext` похожим на путь к файлу.
86
86
  *
87
- * An allow-list, deliberately: enumerating everything that is NOT a file (TLDs,
88
- * brand names, …) is a brittle heuristic that can never be complete. Instead a
89
- * bare `word.ext` is only treated as a file when its extension is a known one —
90
- * so `Gismeteo.by`, `weather.com`, or any future TLD is never flagged, while a
91
- * genuine `config.json` / `helper.ts` still is.
87
+ * Allow-list осознанно: перечислять всё, что НЕ файл (TLD, бренды, …) —
88
+ * хрупкая эвристика, которая никогда не полна. Вместо этого голый `word.ext`
89
+ * считается файлом только при известном расширении — так `Gismeteo.by`,
90
+ * `weather.com` и любой будущий TLD не флагуются, а настоящие
91
+ * `config.json` / `helper.ts` — да.
92
92
  */
93
93
  const FILE_EXTENSIONS = new Set([
94
- // source / code
94
+ // исходники / код
95
95
  "ts",
96
96
  "tsx",
97
97
  "js",
@@ -131,7 +131,7 @@ const FILE_EXTENSIONS = new Set([
131
131
  "vue",
132
132
  "svelte",
133
133
  "dart",
134
- // config / data / docs
134
+ // конфиги / данные / доки
135
135
  "json",
136
136
  "jsonc",
137
137
  "json5",
@@ -143,6 +143,7 @@ const FILE_EXTENSIONS = new Set([
143
143
  "conf",
144
144
  "env",
145
145
  "xml",
146
+ "drawio",
146
147
  "html",
147
148
  "htm",
148
149
  "md",
@@ -162,7 +163,7 @@ const FILE_EXTENSIONS = new Set([
162
163
  "scss",
163
164
  "sass",
164
165
  "less",
165
- // assets / binary
166
+ // ассеты / бинарники
166
167
  "svg",
167
168
  "png",
168
169
  "jpg",
@@ -198,7 +199,7 @@ const FILE_EXTENSIONS = new Set([
198
199
  "a",
199
200
  "dylib",
200
201
  ]);
201
- /** Well-known runtime/library names that end in .js/.ts but are NOT files. */
202
+ /** Известные рантаймы/библиотеки на .js/.ts, которые НЕ файлы. */
202
203
  const TECH_NAMES = new Set([
203
204
  "node.js",
204
205
  "react.js",
@@ -213,12 +214,12 @@ const TECH_NAMES = new Set([
213
214
  ]);
214
215
  const FILE_TOKEN_RE = /\b(?:[a-zA-Z]:[\\/])?[\w./\\-]+\.[a-z]{1,6}\b/gi;
215
216
  /**
216
- * True when `candidate` is a plausible file path token: for a path with a
217
- * directory separator the last segment must carry a non-empty stem before the
218
- * extension (extension-only segments like `tsx/.ts` are rejected), while a bare
219
- * `word.ext` is only a file when its extension is a known file extension (so
220
- * domains like `Gismeteo.by` / `weather.com` are never files). JS member access
221
- * (`process.env`) and technology names (`node.js`) are never files.
217
+ * True, если `candidate` — правдоподобный токен пути: у пути с разделителем
218
+ * последний сегмент обязан нести непустой stem перед расширением (сегменты
219
+ * только из расширения вроде `tsx/.ts` отбрасываются), а голый `word.ext` —
220
+ * файл только при известном расширении (домены `Gismeteo.by` / `weather.com`
221
+ * — никогда файлы). JS member access (`process.env`) и техно-имена
222
+ * (`node.js`) — никогда файлы.
222
223
  */
223
224
  export function isFilePathLike(s) {
224
225
  const lower = s.toLowerCase();
@@ -232,12 +233,11 @@ export function isFilePathLike(s) {
232
233
  return false;
233
234
  return FILE_EXTENSIONS.has(ext);
234
235
  }
235
- // Tokens with a directory separator: require the LAST segment to carry a
236
- // real file name (a non-empty stem before the extension). Otherwise an
237
- // extension-only segment like "tsx/.ts" (from ".tsx/.ts" in a step
238
- // description) or a dotfile ("src/.env") is misread as a file and the plan
239
- // deliverable gate demands "create tsx/.ts" (observed in ses_mstda6uo:
240
- // step "Проверить что остались только .tsx/.ts файлы").
236
+ // Токены с разделителем: требуем у ПОСЛЕДНЕГО сегмента настоящее имя
237
+ // (непустой stem перед расширением). Иначе сегмент только из расширения
238
+ // вроде "tsx/.ts" (из ".tsx/.ts" в описании шага) или дотфайл ("src/.env")
239
+ // читается как файл, и deliverable-gate требует "создать tsx/.ts"
240
+ // (было в ses_mstda6uo: шаг "Проверить что остались только .tsx/.ts файлы").
241
241
  const last = s.split(/[\\/]/).pop() ?? "";
242
242
  const lastDot = last.lastIndexOf(".");
243
243
  if (lastDot <= 0)
@@ -245,18 +245,17 @@ export function isFilePathLike(s) {
245
245
  return true;
246
246
  }
247
247
  /**
248
- * Extract deduplicated file-like tokens from free text. Bare `word.ext` tokens
249
- * are filtered through `isFilePathLike` (allow-list of real file extensions, so
250
- * hostnames/TLDs like `whom-pointing-jewel-maine.trycloudflare.com` or
251
- * `Gismeteo.by` never survive); URLs are stripped first so
252
- * `https://host/repo.git` never survives as a "file".
248
+ * Извлекает дедуплицированные файлоподобные токены из свободного текста.
249
+ * Голые `word.ext` фильтруются через `isFilePathLike` (allow-list реальных
250
+ * расширений — хосты/TLD вроде `whom-pointing-jewel-maine.trycloudflare.com`
251
+ * или `Gismeteo.by` не выживают); URL вырезаются заранее, чтобы
252
+ * `https://host/repo.git` не выжил как "файл".
253
253
  */
254
254
  export function extractFileLikeTokens(text) {
255
255
  const matches = text.match(FILE_TOKEN_RE) || [];
256
256
  return [...new Set(matches.filter(isFilePathLike))];
257
257
  }
258
- /** Remove URL tokens (scheme://… up to whitespace/quote) so they are never
259
- * parsed as file paths. */
258
+ /** Вырезает URL (scheme://… до пробела/кавычки), чтобы они не парсились как пути. */
260
259
  export function stripUrls(text) {
261
260
  return text.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, " ");
262
261
  }
@@ -1,21 +1,24 @@
1
+ // LLM-судья консистентности: противоречит ли новый ответ ранним решениям.
1
2
  import { t } from "../../i18n/index";
2
- const JUDGE_TIMEOUT_MS = 15000;
3
+ import { HALLUCINATION_JUDGE_TIMEOUT_MS as JUDGE_TIMEOUT_MS } from "../../config/timeouts";
3
4
  const MAX_DECISIONS_SHOWN = 6;
4
5
  const MAX_RESPONSE_CHARS = 2000;
5
6
  /**
6
- * LLM-as-judge consistency check. Asks a model whether the agent's latest
7
- * response contradicts any of its earlier decisions — replacing the fragile
8
- * word-matching heuristic that only caught exact substring reversals.
7
+ * Консистентность через LLM-судью. Спрашивает модель, противоречит ли свежий
8
+ * ответ агента его ранним решениям — вместо хрупкой эвристики совпадения слов,
9
+ * ловившей только дословные развороты.
9
10
  *
10
- * Fast path: when no decisions are tracked, returns pass without any LLM call.
11
- * Any judge failure (timeout, provider error, unparseable verdict) degrades to
12
- * pass so the agent loop is never blocked by the judge.
11
+ * Быстрый путь: нет отслеживаемых решений — pass без LLM-вызова.
12
+ * Любой сбой судьи (таймаут, ошибка провайдера, непарсящийся вердикт) —
13
+ * деградация в pass, чтобы цикл агента не блокировался судьёй.
13
14
  */
15
+ /** LLM-судья: проверяет новый ответ на противоречие ранним решениям. */
14
16
  export class LLMJudge {
15
17
  provider;
16
18
  constructor(provider) {
17
19
  this.provider = provider;
18
20
  }
21
+ /** Валидирует ответ против решений: warn при противоречии, иначе pass (сбои — тоже pass). */
19
22
  async validate(response, consistency) {
20
23
  const decisions = consistency.getDecisions();
21
24
  if (decisions.length === 0) {
@@ -54,6 +57,7 @@ Reply with ONLY valid JSON, no other text:
54
57
  return { status: "pass" };
55
58
  }
56
59
  }
60
+ /** Один запрос к судье со сбором текстовых чанков. */
57
61
  async askJudge(messages) {
58
62
  let text = "";
59
63
  const controller = new AbortController();
@@ -66,8 +70,8 @@ Reply with ONLY valid JSON, no other text:
66
70
  }
67
71
  return text;
68
72
  })();
69
- // The timeout can settle the race while the background stream is still
70
- // running; its later rejection must not surface as an unhandled rejection.
73
+ // Таймаут может завершить гонку, пока фоновый стрим ещё идёт;
74
+ // его позднее rejection не должно всплыть как unhandled rejection.
71
75
  consume.catch(() => { });
72
76
  const timeout = new Promise((_, reject) => {
73
77
  timer = setTimeout(() => {
@@ -81,11 +85,12 @@ Reply with ONLY valid JSON, no other text:
81
85
  finally {
82
86
  if (timer)
83
87
  clearTimeout(timer);
84
- // Abort the background stream on the success path too, so a judge that
85
- // resolved early never keeps streaming in the background.
88
+ // Абортим фоновый стрим и на успешном пути, чтобы рано завершившийся
89
+ // судья не продолжал стримить в фоне.
86
90
  controller.abort();
87
91
  }
88
92
  }
93
+ /** Парсит вердикт: строгий JSON → терпимый разбор, иначе "не противоречит". */
89
94
  parseVerdict(text) {
90
95
  const block = text.match(/\{[\s\S]*\}/);
91
96
  if (block) {
@@ -99,7 +104,7 @@ Reply with ONLY valid JSON, no other text:
99
104
  }
100
105
  }
101
106
  catch {
102
- // fall through to lenient parsing
107
+ // Не парсится строго — ниже терпимый разбор
103
108
  }
104
109
  }
105
110
  const lower = text.toLowerCase();
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Проверка заявлений об успехе (ses_muih0ah5): модель объявила "MMA 2.8.0
3
+ * опубликован", а её же тулы показывали `latest: 2.5.0` и 404 на тарболле
4
+ * 2.8.0. Factual-check валидирует только пути файлов, так что сильное
5
+ * заявление об успехе вразрез со свежими доказательствами прошло молча.
6
+ *
7
+ * Полностью детерминирована (regex + флаги успеха тулов, без LLM): сильное
8
+ * заявление о завершении + сигналы ошибок в свежих выводах тулов + отсутствие
9
+ * хеджирования в ответе = противоречие. Точность осознанно смещена в recall —
10
+ * ложное срабатывание стоит одну ограниченную re-check итерацию
11
+ * (MAX_CLAIM_CHECK_RETRIES), пропуск — статус-кво.
12
+ */
13
+ /**
14
+ * Помощник границ слов. `\b`/`\w` в JS — только ASCII, поэтому кириллические
15
+ * окончания (опубликован**а**, ошибк**а**) их не матчат — границы выписаны явно
16
+ * для обоих алфавитов. Lookbehind поддерживают все рантаймы MMA (Node ≥20, Bun).
17
+ */
18
+ const WORD = "A-Za-zА-Яа-яЁё0-9_";
19
+ const wb = (body) => new RegExp(`(?<![${WORD}])(?:${body})(?![${WORD}])`, "i");
20
+ /** Сильные, нехеджированные заявления о завершении (любая локаль агента). */
21
+ const SUCCESS_CLAIM_RE = wb("published|опубликован[а-яё]*|задеплоен[а-яё]*|deployed|shipped|готово|выполнено|завершен[а-яё]*|успешно|success(?:ful(?:ly)?)?|done|completed?|finished|все сделано|всё сделано");
22
+ /** Ответ уже хеджирован — модель учла неопределённость, пропускаем. */
23
+ const HEDGE_RE = wb("may take|being processed|processing|staged|propagat[a-z]*|in progress|shortly|soon|will appear|несколько минут|обрабатывается|застейджен[а-яё]*|подожд[а-яё]*|проверь[а-яё]* позже|появится|пропагир[а-яё]*|кэш[а-яё]*|cach[a-z]*|обычно|typically");
24
+ /** Сигналы ошибок в выводе тулов (узко: коды + явные фейлы). */
25
+ const FAILURE_SIGNAL_RE = new RegExp(`${wb("404|409|40[03]|50\\d|failed|failure|not found|ERR_[A-Z_]+|E(?:404|409|ACCES|PERM|CONNREFUSED|TIMED?OUT)|conflict|denied|ошибка|ошибк[а-яё]*|не найден[а-яё]*|не удалось|отклонено").source}|✗|⛔`, "i");
26
+ const EVIDENCE_WINDOW = 10;
27
+ const EVIDENCE_TEXT_CAP = 400;
28
+ const SNIPPET_MAX_CHARS = 160;
29
+ /**
30
+ * Возвращает противоречащее доказательство, или null если ответ в порядке:
31
+ * нет заявления об успехе, есть хеджирование, или нет сигналов ошибок в свежих тулах.
32
+ */
33
+ export function checkSuccessClaim(response, tools) {
34
+ if (!response || !SUCCESS_CLAIM_RE.test(response))
35
+ return null;
36
+ if (HEDGE_RE.test(response))
37
+ return null;
38
+ const recent = tools.filter((t) => t.text?.trim()).slice(-EVIDENCE_WINDOW);
39
+ // Свежайшее противоречие первым — цитируется самое актуальное доказательство.
40
+ for (let i = recent.length - 1; i >= 0; i--) {
41
+ const entry = recent[i];
42
+ const text = entry.text.slice(0, EVIDENCE_TEXT_CAP);
43
+ if (entry.ok === false || FAILURE_SIGNAL_RE.test(text)) {
44
+ const firstLine = text
45
+ .split("\n")
46
+ .map((l) => l.trim())
47
+ .find((l) => l.length > 0);
48
+ if (!firstLine)
49
+ continue;
50
+ return {
51
+ tool: entry.name || "tool",
52
+ snippet: firstLine.length > SNIPPET_MAX_CHARS
53
+ ? `${firstLine.slice(0, SNIPPET_MAX_CHARS - 1)}…`
54
+ : firstLine,
55
+ };
56
+ }
57
+ }
58
+ return null;
59
+ }
@@ -1,31 +1,64 @@
1
+ // Дисковый кэш индекса проекта: envelope с версией, root и сигнатурой дерева.
1
2
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "fs";
2
3
  import { join } from "path";
4
+ /**
5
+ * Инкремент при смене envelope или формы `IndexResult`: кэши старого MMA
6
+ * считаются протухшими и перестраиваются.
7
+ */
8
+ /** Версия схемы кэша индекса. */
9
+ export const CACHE_VERSION = 2;
10
+ /** Дисковый кэш индекса с валидацией версии/root/сигнатуры. */
3
11
  export class IndexCache {
4
12
  cachePath;
5
13
  cache = null;
6
- constructor(cacheDir) {
14
+ root;
15
+ signature;
16
+ constructor(cacheDir, opts = {}) {
7
17
  this.cachePath = join(cacheDir, "index-cache.json");
18
+ this.root = opts.root;
19
+ this.signature = opts.signature;
8
20
  }
21
+ /**
22
+ * Возвращает кэшированный индекс, только если он описывает текущее дерево.
23
+ * Отвергает кэш при расхождении версии схемы, сохранённого `root` или
24
+ * `signature`; легаси-кэш (сырой `IndexResult` без envelope) считается
25
+ * протухшим. Вызывающие на `null` делают полный обход.
26
+ */
9
27
  load() {
10
28
  if (this.cache)
11
29
  return this.cache;
12
30
  if (!existsSync(this.cachePath))
13
31
  return null;
14
32
  try {
15
- this.cache = JSON.parse(readFileSync(this.cachePath, "utf-8"));
33
+ const parsed = JSON.parse(readFileSync(this.cachePath, "utf-8"));
34
+ if (parsed.version !== CACHE_VERSION || !parsed.result)
35
+ return null;
36
+ if (this.root !== undefined && parsed.root !== this.root)
37
+ return null;
38
+ if (this.signature && parsed.signature !== this.currentSignature())
39
+ return null;
40
+ this.cache = parsed.result;
16
41
  return this.cache;
17
42
  }
18
43
  catch {
19
44
  return null;
20
45
  }
21
46
  }
47
+ /** Сохраняет результат индекса в кэш (память + диск). */
22
48
  save(result) {
23
49
  this.cache = result;
50
+ const envelope = {
51
+ version: CACHE_VERSION,
52
+ root: this.root ?? "",
53
+ signature: this.currentSignature(),
54
+ result,
55
+ };
24
56
  const dir = join(this.cachePath, "..");
25
57
  if (!existsSync(dir))
26
58
  mkdirSync(dir, { recursive: true });
27
- writeFileSync(this.cachePath, JSON.stringify(result), "utf-8");
59
+ writeFileSync(this.cachePath, JSON.stringify(envelope), "utf-8");
28
60
  }
61
+ /** Инвалидирует кэш (память + дисковый файл). */
29
62
  invalidate() {
30
63
  this.cache = null;
31
64
  if (existsSync(this.cachePath)) {
@@ -33,8 +66,23 @@ export class IndexCache {
33
66
  rmSync(this.cachePath);
34
67
  }
35
68
  catch {
36
- /* ignore */
69
+ /* best-effort очистка */
37
70
  }
38
71
  }
39
72
  }
73
+ /**
74
+ * Считает сигнатуру дерева защищённо: нечитаемое дерево даёт пустую строку,
75
+ * которая не совпадёт с сохранённой успешным обходом — битое дерево
76
+ * инвалидирует кэш, а не вызывает доверие к нему.
77
+ */
78
+ currentSignature() {
79
+ if (!this.signature)
80
+ return "";
81
+ try {
82
+ return this.signature();
83
+ }
84
+ catch {
85
+ return "";
86
+ }
87
+ }
40
88
  }
@@ -1,3 +1,4 @@
1
+ // Индекс проекта: реэкспорт вокера, кэша и модуля карты проекта.
1
2
  export { Indexer } from "./walker";
2
3
  export { IndexCache } from "./cache";
3
4
  export { IndexerModule } from "./module";
@@ -1,8 +1,7 @@
1
1
  import { t } from "../../i18n/index";
2
2
  /**
3
- * Shared implementation of the project-map command used by both `mma map`
4
- * (CLI) and `/map` (REPL): summary (the exact text injected into the system
5
- * prompt), refresh, and find.
3
+ * Общая реализация команды карты проекта для `mma map` (CLI) и `/map` (REPL):
4
+ * summary (точный текст из системного промпта), refresh и find.
6
5
  */
7
6
  export async function runMapAction(module, action, query) {
8
7
  const summary = async () => {
@@ -28,7 +27,6 @@ export async function runMapAction(module, action, query) {
28
27
  await module.buildIndex();
29
28
  return { ok: true, output: module.formatFind(q) };
30
29
  }
31
- case "summary":
32
30
  default:
33
31
  return summary();
34
32
  }