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,7 +1,9 @@
1
+ // CLI-команды сертификации: прогон сценариев, статусы, список и снятие меток.
1
2
  import { rmSync, writeFileSync } from "fs";
2
3
  import { join, dirname } from "path";
3
4
  import { fileURLToPath } from "url";
4
5
  import { existsSync, readFileSync } from "fs";
6
+ import { projectMmaDir } from "../../config/paths";
5
7
  import { t } from "../../i18n/index";
6
8
  import { pc } from "../../ui/colors";
7
9
  import { loadScenarios, filterByTags, filterByScenarios, unknownScenarioIds } from "./loader";
@@ -20,21 +22,26 @@ function readVersion() {
20
22
  return raw.version;
21
23
  }
22
24
  catch {
23
- // Broken package.json — fall back to the default version
25
+ // Битый package.json — откат на версию по умолчанию
24
26
  }
25
27
  }
26
28
  }
27
29
  return "0.0.0";
28
30
  }
31
+ /** Разбирает строку тегов через запятую в список `CertTag`. */
29
32
  export function parseTags(s) {
30
33
  return s
31
34
  .split(",")
32
35
  .map((x) => x.trim())
33
36
  .filter(Boolean);
34
37
  }
38
+ /**
39
+ * Прогоняет выбранные сценарии на модели и сохраняет запись в манифест.
40
+ * Частичный прогон (по id) мержится с предыдущей записью.
41
+ */
35
42
  export async function certify(opts) {
36
43
  const providerUrl = opts.providerUrl || opts.config.provider.baseUrl;
37
- const { scenarios, errors } = loadScenarios(join(opts.projectDir, ".mma", "certification", "scenarios"));
44
+ const { scenarios, errors } = loadScenarios(join(projectMmaDir(opts.projectDir), "certification", "scenarios"));
38
45
  for (const e of errors)
39
46
  getDefaultChannel().writeLine(pc.yellow(` ${e}`), "stderr");
40
47
  let selected = filterByTags(scenarios, opts.tags);
@@ -49,10 +56,9 @@ export async function certify(opts) {
49
56
  process.exitCode = 1;
50
57
  return;
51
58
  }
52
- // A targeted re-run selects from ALL loaded scenarios (union of their
53
- // tags), not the tag-filtered subset — otherwise re-running a security
54
- // scenario with default core-only tags silently produced an empty
55
- // selection.
59
+ // Точечный перепрогон выбирает из ВСЕХ загруженных сценариев (объединение
60
+ // тегов), а не из отфильтрованного по тегам подмножества — иначе перепрогон
61
+ // security-сценария с дефолтными core-тегами молча давал пустую выборку.
56
62
  selected = filterByScenarios(scenarios, opts.scenarios);
57
63
  }
58
64
  if (selected.length === 0) {
@@ -62,8 +68,8 @@ export async function certify(opts) {
62
68
  }
63
69
  const manifest = readMergedManifest(opts.projectDir);
64
70
  const existing = manifest.certifications.find((e) => e.model === opts.name && e.providerUrl === providerUrl);
65
- // A full-suite re-run overwrites the entry and requires --force; a partial
66
- // (scenario-targeted) run is an incremental update by definition.
71
+ // Полный перепрогон перезаписывает запись и требует --force; частичный
72
+ // прогон (по id сценариев) по определению — инкрементальное обновление.
67
73
  if (existing && !opts.force && !isPartial) {
68
74
  getDefaultChannel().writeLine(pc.yellow(t("cli.cert_exists", { model: opts.name })), "stderr");
69
75
  getDefaultChannel().writeLine(pc.yellow(t("cli.cert_exists_hint")), "stderr");
@@ -71,7 +77,7 @@ export async function certify(opts) {
71
77
  return;
72
78
  }
73
79
  getDefaultChannel().writeLine(t("cli.cert_started", { model: opts.name, provider: providerUrl }));
74
- const sandboxBase = join(process.cwd(), ".mma", "certification");
80
+ const sandboxBase = join(projectMmaDir(process.cwd()), "certification");
75
81
  const results = [];
76
82
  const total = selected.length;
77
83
  let idx = 0;
@@ -116,13 +122,13 @@ export async function certify(opts) {
116
122
  /* ignore */
117
123
  }
118
124
  }
119
- // Partial run: keep previous results for scenarios that were not re-run.
125
+ // Частичный прогон: сохраняем прежние результаты неперегнанных сценариев.
120
126
  const runIds = new Set(selected.map((s) => s.id));
121
127
  const finalResults = isPartial && existing ? mergePartialResults(results, existing.results, runIds) : results;
122
128
  const suite = summarize(finalResults);
123
- // Only a re-run that covers EVERY scenario in the suite may refresh
124
- // mmaVersion — a partial re-run leaves most results from an older MMA, so
125
- // stamping the current version would fake a fresh certification.
129
+ // Только прогон, покрывший ВСЕ сценарии набора, вправе обновить
130
+ // mmaVersion — частичный оставляет большинство результатов от старого MMA,
131
+ // и штамп текущей версии выдавал бы их за свежую сертификацию.
126
132
  const ranEntireSuite = scenarios.every((s) => runIds.has(s.id));
127
133
  const entry = {
128
134
  model: opts.name,
@@ -132,9 +138,9 @@ export async function certify(opts) {
132
138
  suite,
133
139
  results: finalResults,
134
140
  };
135
- // Certifications are per (model, providerUrl) — machine-scoped, not
136
- // project-scoped: store them in the global manifest so marks follow the
137
- // user into any directory.
141
+ // Сертификации привязаны к паре (модель, providerUrl) — это машинный, а не
142
+ // проектный скоуп: храним в глобальном манифесте, чтобы метки следовали
143
+ // за пользователем в любой каталог.
138
144
  upsertCertification(entry);
139
145
  getDefaultChannel().writeLine(t("cli.cert_done", {
140
146
  passed: String(suite.passed),
@@ -149,9 +155,10 @@ export async function certify(opts) {
149
155
  else {
150
156
  getDefaultChannel().writeLine(pc.yellow(`\n⚠ ${opts.name} is NOT certified — all scenarios must pass`));
151
157
  }
152
- // Always write report file
158
+ // Файл отчёта пишется всегда
153
159
  writeReport(entry, opts.projectDir);
154
160
  }
161
+ /** Печатает статус сертификации модели (провайдер, версия, сводка, сценарии). */
155
162
  export async function certStatus(name, config, projectDir) {
156
163
  const m = readMergedManifest(projectDir);
157
164
  const providerUrl = config.provider.baseUrl;
@@ -167,6 +174,7 @@ export async function certStatus(name, config, projectDir) {
167
174
  getDefaultChannel().writeLine(`Status: ${status}`);
168
175
  printResults(entry.results);
169
176
  }
177
+ /** Печатает все записи манифеста (модель, провайдер, версия, счёт). */
170
178
  export async function certList(projectDir) {
171
179
  const m = readMergedManifest(projectDir);
172
180
  if (m.certifications.length === 0) {
@@ -178,6 +186,7 @@ export async function certList(projectDir) {
178
186
  getDefaultChannel().writeLine(` ${mark} ${e.model} ${pc.dim(e.providerUrl)} ${e.mmaVersion} ${e.certifiedAt.slice(0, 10)} ${e.suite.passed}/${e.suite.total} pass`);
179
187
  }
180
188
  }
189
+ /** Удаляет запись о сертификации модели из глобального и проектного манифестов. */
181
190
  export async function uncertify(name, config, projectDir) {
182
191
  const removed = removeCertification(name, config.provider.baseUrl, {
183
192
  projectDir,
@@ -196,9 +205,8 @@ function summarize(results) {
196
205
  };
197
206
  }
198
207
  /**
199
- * Merge a partial run's results with a previous certification: fresh results
200
- * replace old ones by scenario id; results for scenarios that were not re-run
201
- * are preserved verbatim.
208
+ * Сливает результаты частичного прогона с прежней сертификацией: свежие
209
+ * заменяют старые по id сценария; неперегнанные сохраняются как есть.
202
210
  */
203
211
  export function mergePartialResults(fresh, previous, rerunIds) {
204
212
  const preserved = previous.filter((r) => !rerunIds.has(r.id));
@@ -1,5 +1,10 @@
1
+ // Проверка результатов сценария в песочнице: файлы, вывод, код выхода.
1
2
  import { existsSync, readFileSync, statSync } from "fs";
2
3
  import { join } from "path";
4
+ /**
5
+ * Прогоняет проверки по состоянию песочницы.
6
+ * Возвращает общий вердикт и описания провалов.
7
+ */
3
8
  export function checkSandbox(sandboxDir, checks, exitCode, output) {
4
9
  const failures = [];
5
10
  for (const check of checks) {
@@ -14,7 +19,10 @@ function runCheck(sandboxDir, check, exitCode, output) {
14
19
  case "exitCode":
15
20
  return exitCode === (check.code ?? 0);
16
21
  case "outputContains":
17
- return output.includes(check.text);
22
+ // Без учёта регистра: security-маркер выводится капсом ("[SECURITY BLOCKED]"),
23
+ // а строгое сравнение заставляло security-сценарии зависеть от того,
24
+ // продублирует ли модель слово "security" строчными.
25
+ return output.toLowerCase().includes(check.text.toLowerCase());
18
26
  case "fileExists":
19
27
  return isFile(join(sandboxDir, check.path));
20
28
  case "fileNotExists":
@@ -1,3 +1,4 @@
1
+ // Загрузка сценариев сертификации: встроенные плюс пользовательские YAML из каталога.
1
2
  import { existsSync, readdirSync, readFileSync } from "fs";
2
3
  import { join } from "path";
3
4
  import { parse as parseYaml } from "yaml";
@@ -12,6 +13,7 @@ const CHECK_TYPES = [
12
13
  "exitCode",
13
14
  "outputContains",
14
15
  ];
16
+ /** Проверяет сценарий, возвращает список ошибок (пусто — валиден). */
15
17
  export function validateScenario(s) {
16
18
  const errors = [];
17
19
  const isSkip = s.mode === "skip";
@@ -54,6 +56,10 @@ export function validateScenario(s) {
54
56
  }
55
57
  return errors;
56
58
  }
59
+ /**
60
+ * Загружает встроенные сценарии и пользовательские YAML из `userDir`.
61
+ * Битые описания отбрасываются с записью ошибки в `errors`.
62
+ */
57
63
  export function loadScenarios(userDir) {
58
64
  const errors = [];
59
65
  const scenarios = [];
@@ -102,18 +108,19 @@ function normalizeScenario(data, file) {
102
108
  config: typeof d.config === "object" && d.config !== null ? d.config : undefined,
103
109
  };
104
110
  }
111
+ /** Фильтрует сценарии по тегам; без тегов оставляет только `core`. */
105
112
  export function filterByTags(scenarios, tags) {
106
113
  if (!tags || tags.length === 0) {
107
114
  return scenarios.filter((s) => s.tags.includes("core"));
108
115
  }
109
116
  return scenarios.filter((s) => s.tags.some((t) => tags.includes(t)));
110
117
  }
111
- /** Keep only scenarios whose id is in the list (exact, case-insensitive match). */
118
+ /** Оставляет сценарии с id из списка (точное совпадение без учёта регистра). */
112
119
  export function filterByScenarios(scenarios, ids) {
113
120
  const wanted = new Set(ids.map((id) => id.toLowerCase()));
114
121
  return scenarios.filter((s) => wanted.has(s.id.toLowerCase()));
115
122
  }
116
- /** Return ids from the request that do not exist among loaded scenarios. */
123
+ /** Возвращает запрошенные id, которых нет среди загруженных сценариев. */
117
124
  export function unknownScenarioIds(scenarios, ids) {
118
125
  const known = new Set(scenarios.map((s) => s.id.toLowerCase()));
119
126
  return ids.filter((id) => !known.has(id.toLowerCase()));
@@ -1,17 +1,23 @@
1
+ // Манифест сертификации: проектный и глобальный (per-user) файлы, мерж и синк bundled-копии.
1
2
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
2
3
  import { dirname, join } from "path";
3
4
  import { fileURLToPath } from "url";
4
5
  import { homedir } from "os";
6
+ import { MMA_DIR_NAME } from "../../config/paths";
7
+ import { readEnv } from "../../config/env";
8
+ /** Путь к проектному манифесту внутри каталога проекта. */
5
9
  export function manifestPath(projectDir) {
6
10
  return join(projectDir, "certification", "certifications.json");
7
11
  }
8
- /** Directory of the global (per-user) manifest. Overridable for tests. */
12
+ /** Каталог глобального (per-user) манифеста. Переопределяется для тестов. */
9
13
  export function globalManifestDir(override) {
10
- return override || process.env.MMA_CERT_HOME || join(homedir(), ".mma");
14
+ return override || readEnv("CERT_HOME") || join(homedir(), MMA_DIR_NAME);
11
15
  }
16
+ /** Путь к глобальному манифесту. */
12
17
  export function globalManifestPath(override) {
13
18
  return join(globalManifestDir(override), "certifications.json");
14
19
  }
20
+ /** Читает проектный манифест; битый/отсутствующий даёт пустой. */
15
21
  export function readManifest(projectDir) {
16
22
  const path = manifestPath(projectDir);
17
23
  try {
@@ -21,15 +27,17 @@ export function readManifest(projectDir) {
21
27
  }
22
28
  }
23
29
  catch {
24
- /* corrupted manifest → start fresh */
30
+ /* битый манифест → начинаем с чистого */
25
31
  }
26
32
  return { version: 1, certifications: [] };
27
33
  }
34
+ /** Сохраняет проектный манифест (каталог создаётся). */
28
35
  export function saveManifest(m, projectDir) {
29
36
  const path = manifestPath(projectDir);
30
37
  mkdirSync(join(projectDir, "certification"), { recursive: true });
31
38
  writeFileSync(path, JSON.stringify(m, null, 2), "utf-8");
32
39
  }
40
+ /** Читает глобальный манифест; битый/отсутствующий даёт пустой. */
33
41
  export function readGlobalManifest(override) {
34
42
  const path = globalManifestPath(override);
35
43
  try {
@@ -39,10 +47,11 @@ export function readGlobalManifest(override) {
39
47
  }
40
48
  }
41
49
  catch {
42
- /* corrupted global manifest → start fresh */
50
+ /* битый глобальный манифест → начинаем с чистого */
43
51
  }
44
52
  return { version: 1, certifications: [] };
45
53
  }
54
+ /** Сохраняет глобальный манифест. */
46
55
  export function saveGlobalManifest(m, override) {
47
56
  const dir = globalManifestDir(override);
48
57
  mkdirSync(dir, { recursive: true });
@@ -50,17 +59,26 @@ export function saveGlobalManifest(m, override) {
50
59
  }
51
60
  const certKey = (e) => `${e.model}::${e.providerUrl}`;
52
61
  /**
53
- * Global + project manifests merged by (model, providerUrl).
54
- * Project entries win over global ones on the same key.
62
+ * Глобальный + проектный манифесты, сведённые по (model, providerUrl). Побеждает
63
+ * БОЛЕЕ НОВАЯ запись (сначала mmaVersion, затем certifiedAt); легаси-проектный
64
+ * манифест свежую глобальную сертификацию больше не затеняет.
55
65
  */
56
66
  export function readMergedManifest(projectDir, override) {
57
67
  const merged = new Map();
58
68
  for (const e of readGlobalManifest(override).certifications)
59
69
  merged.set(certKey(e), e);
60
- for (const e of readManifest(projectDir).certifications)
61
- merged.set(certKey(e), e);
70
+ for (const e of readManifest(projectDir).certifications) {
71
+ const key = certKey(e);
72
+ const existing = merged.get(key);
73
+ if (!existing || isEntryNewer(e, existing))
74
+ merged.set(key, e);
75
+ }
62
76
  return { version: 1, certifications: [...merged.values()] };
63
77
  }
78
+ /**
79
+ * Добавляет/обновляет запись в глобальном манифесте по ключу (model, providerUrl).
80
+ * Возвращает обновлённый манифест.
81
+ */
64
82
  export function upsertCertification(entry, override) {
65
83
  const m = readGlobalManifest(override);
66
84
  const idx = m.certifications.findIndex((e) => certKey(e) === certKey(entry));
@@ -71,6 +89,10 @@ export function upsertCertification(entry, override) {
71
89
  saveGlobalManifest(m, override);
72
90
  return m;
73
91
  }
92
+ /**
93
+ * Удаляет запись из глобального и легаси-проектного манифестов.
94
+ * Возвращает флаги, откуда реально удалили.
95
+ */
74
96
  export function removeCertification(model, providerUrl, opts) {
75
97
  const key = `${model}::${providerUrl}`;
76
98
  const g = readGlobalManifest(opts?.globalDir);
@@ -79,7 +101,7 @@ export function removeCertification(model, providerUrl, opts) {
79
101
  const removedGlobal = g.certifications.length !== beforeG;
80
102
  if (removedGlobal)
81
103
  saveGlobalManifest(g, opts?.globalDir);
82
- // Legacy per-project manifests may still hold the entry — clean those too.
104
+ // В легаси-проектных манифестах запись могла остаться — чистим и их.
83
105
  let removedProject = false;
84
106
  try {
85
107
  const projectDir = opts?.projectDir ?? process.cwd();
@@ -92,16 +114,49 @@ export function removeCertification(model, providerUrl, opts) {
92
114
  }
93
115
  }
94
116
  catch {
95
- /* no project manifest — fine */
117
+ /* проектного манифеста нет — нормально */
96
118
  }
97
119
  return { global: removedGlobal, project: removedProject };
98
120
  }
121
+ /** True, если запись сделана не на текущей версии MMA (протухла). */
99
122
  export function isStale(entry, currentVersion) {
100
123
  return entry.mmaVersion !== currentVersion;
101
124
  }
125
+ /** Numeric semver-ish comparison; non-numeric/missing parts count as 0. */
126
+ function compareVersions(a, b) {
127
+ const pa = String(a ?? "").split(".").map((n) => parseInt(n, 10) || 0);
128
+ const pb = String(b ?? "").split(".").map((n) => parseInt(n, 10) || 0);
129
+ const len = Math.max(pa.length, pb.length);
130
+ for (let i = 0; i < len; i++) {
131
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
132
+ if (d !== 0)
133
+ return d > 0 ? 1 : -1;
134
+ }
135
+ return 0;
136
+ }
137
+ /**
138
+ * Действительно ли `candidate` новее `current` (сначала mmaVersion, затем
139
+ * certifiedAt)? Защита от даунгрейда свежей сертификации — и при синке
140
+ * bundled-манифеста, и при мерже легаси-проектного.
141
+ */
142
+ function isEntryNewer(candidate, current) {
143
+ const v = compareVersions(candidate.mmaVersion, current.mmaVersion);
144
+ if (v !== 0)
145
+ return v > 0;
146
+ const ec = Date.parse(candidate.certifiedAt ?? "");
147
+ const ee = Date.parse(current.certifiedAt ?? "");
148
+ if (!Number.isNaN(ec) && !Number.isNaN(ee))
149
+ return ec > ee;
150
+ return false;
151
+ }
152
+ /** True, если в записи нет провалов и есть хоть один пройденный сценарий. */
102
153
  export function isFullyPassed(entry) {
103
154
  return entry.suite.failed === 0 && entry.suite.passed > 0;
104
155
  }
156
+ /**
157
+ * Метка модели: `certified` (всё прошло на текущей версии), `stale` (запись от
158
+ * старой версии) или `none` (нет записи / есть провалы).
159
+ */
105
160
  export function getCertMark(model, providerUrl, currentVersion, projectDir, globalOverride) {
106
161
  const m = readMergedManifest(projectDir, globalOverride);
107
162
  const entry = m.certifications.find((e) => e.model === model && e.providerUrl === providerUrl);
@@ -116,9 +171,9 @@ function syncSnapshotPath(override) {
116
171
  return join(globalManifestDir(override), "certifications.synced.json");
117
172
  }
118
173
  /**
119
- * Locate the certifications.json bundled with the installed MMA package.
120
- * - dist/main.js layout: <dist>/certification/certifications.json (copied by copy-assets)
121
- * - source layout: <repo-root>/certification/certifications.json
174
+ * Ищет certifications.json, упакованный в установленный пакет MMA.
175
+ * - бандл dist/main.js: <dist>/certification/certifications.json (копирует copy-assets)
176
+ * - исходники: <repo-root>/certification/certifications.json
122
177
  */
123
178
  export function bundledManifestPath() {
124
179
  const here = dirname(fileURLToPath(import.meta.url));
@@ -134,12 +189,12 @@ export function bundledManifestPath() {
134
189
  return null;
135
190
  }
136
191
  /**
137
- * Sync the bundled (shipped-with-package) manifest into the global
138
- * ~/.mma/certifications.json. Runs once per process start:
139
- * the bundled file overwrites its own entries on every package update;
140
- * user-local certifications for OTHER keys are preserved. A snapshot of the
141
- * raw bundled content marks what was already synced, so nothing is rewritten
142
- * between updates.
192
+ * Вливает упакованный (поставляемый с пакетом) манифест в глобальный
193
+ * ~/.mma/certifications.json. Работает раз на старт процесса:
194
+ * bundled-файл при каждом обновлении пакета перезаписывает СВОИ записи;
195
+ * пользовательские сертификации под ДРУГИМИ ключами сохраняются. Снапшот
196
+ * сырого bundled-содержимого фиксирует, что уже синкнуто, чтобы между
197
+ * обновлениями ничего не переписывать.
143
198
  */
144
199
  export function syncGlobalManifest(override, bundledPathOverride) {
145
200
  try {
@@ -156,8 +211,15 @@ export function syncGlobalManifest(override, bundledPathOverride) {
156
211
  const merged = new Map();
157
212
  for (const e of current.certifications)
158
213
  merged.set(certKey(e), e);
159
- for (const e of bundled.certifications ?? [])
160
- merged.set(certKey(e), e);
214
+ for (const e of bundled.certifications ?? []) {
215
+ const key = certKey(e);
216
+ const existing = merged.get(key);
217
+ // Не перезаписываем более свежую пользовательскую сертификацию старой
218
+ // bundled-копией (иначе запуск CLI затирает только что сделанный certify).
219
+ if (existing && !isEntryNewer(e, existing))
220
+ continue;
221
+ merged.set(key, e);
222
+ }
161
223
  saveGlobalManifest({ version: 1, certifications: [...merged.values()] }, override);
162
224
  mkdirSync(globalManifestDir(override), { recursive: true });
163
225
  writeFileSync(snapshotPath, raw, "utf-8");
@@ -1,9 +1,16 @@
1
+ // Прогон сценариев сертификации: песочница на повтор, запуск MMA, проверки.
1
2
  import { spawn } from "child_process";
2
- import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readdirSync, readFileSync } from "fs";
3
+ import { existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readdirSync, readFileSync, } from "fs";
3
4
  import { join, resolve, dirname, relative } from "path";
4
5
  import { checkSandbox } from "./fact-checker";
5
6
  import { killTree } from "../processes/kill-tree";
6
7
  import { toForwardSlash } from "../../tools/path-utils";
8
+ import { projectMmaDir } from "../../config/paths";
9
+ import { MMA_ENV } from "../../config/env";
10
+ /**
11
+ * Прогоняет сценарий N повторов в изолированных песочницах.
12
+ * `pass`, когда прошли не меньше порога; при провале прикладывает диагностику.
13
+ */
7
14
  export async function runScenario(scenario, opts) {
8
15
  if (scenario.mode === "skip") {
9
16
  return {
@@ -39,19 +46,19 @@ export async function runScenario(scenario, opts) {
39
46
  ];
40
47
  const env = {
41
48
  ...process.env,
42
- MMA_MODEL: opts.model,
43
- MMA_PROVIDER_BASEURL: opts.providerUrl,
44
- MMA_CONTEXT_WINDOW: String(opts.contextWindow ?? 32000),
49
+ [MMA_ENV.MODEL]: opts.model,
50
+ [MMA_ENV.PROVIDER_BASEURL]: opts.providerUrl,
51
+ [MMA_ENV.CONTEXT_WINDOW]: String(opts.contextWindow ?? 32000),
45
52
  };
46
53
  if (opts.providerKey)
47
- env.MMA_PROVIDER_APIKEY = opts.providerKey;
48
- // Write cert-specific config if scenario declares overrides
54
+ env[MMA_ENV.PROVIDER_APIKEY] = opts.providerKey;
55
+ // Пишем cert-конфиг, если сценарий объявил оверрайды
49
56
  if (scenario.config && opts.baseConfig) {
50
57
  const merged = deepMergeAny(opts.baseConfig, scenario.config);
51
- const certConfigDir = join(sandbox, ".mma");
58
+ const certConfigDir = projectMmaDir(sandbox);
52
59
  mkdirSync(certConfigDir, { recursive: true });
53
60
  writeFileSync(join(certConfigDir, "config.json"), JSON.stringify(merged, null, 2), "utf-8");
54
- env.MMA_CONFIG_DIR = certConfigDir;
61
+ env[MMA_ENV.CONFIG_DIR] = certConfigDir;
55
62
  }
56
63
  const res = await runner(env, opts.mmaRoot, args, timeoutMs);
57
64
  output = `${res.stdout}\n${res.stderr}`;
@@ -108,10 +115,10 @@ function prepareSandbox(sandbox, scenario, mmaRoot) {
108
115
  cpSync(src, dest);
109
116
  }
110
117
  }
111
- /** Collect diagnostic info from a failed sandbox for actionable feedback. */
118
+ /** Собирает диагностику из проваленной песочницы для понятного фидбэка. */
112
119
  export function collectDiagnostics(sandbox) {
113
120
  const lines = [];
114
- // List user-created files (skip .mma internals)
121
+ // Список созданных пользователем файлов (внутренности .mma пропускаем)
115
122
  const files = listFiles(sandbox, sandbox);
116
123
  if (files.length > 0) {
117
124
  lines.push(` Files created: ${files.join(", ")}`);
@@ -119,8 +126,8 @@ export function collectDiagnostics(sandbox) {
119
126
  else {
120
127
  lines.push(" Files created: (none)");
121
128
  }
122
- // Read active plan if present
123
- const planPath = join(sandbox, ".mma", "plans", "active.json");
129
+ // Читаем активный план, если есть
130
+ const planPath = join(projectMmaDir(sandbox), "plans", "active.json");
124
131
  if (existsSync(planPath)) {
125
132
  try {
126
133
  const plan = JSON.parse(readFileSync(planPath, "utf-8"));
@@ -133,7 +140,7 @@ export function collectDiagnostics(sandbox) {
133
140
  }
134
141
  }
135
142
  catch {
136
- /* corrupt plan — skip */
143
+ /* битый план — пропускаем */
137
144
  }
138
145
  }
139
146
  return lines.join("\n");
@@ -159,12 +166,14 @@ function listFiles(dir, root) {
159
166
  }
160
167
  return result;
161
168
  }
169
+ /** Резолвит точку входа MMA: исходники в dev-дереве, иначе собранный бандл. */
162
170
  export function resolveMmaEntry(mmaRoot) {
163
171
  const dev = join(mmaRoot, "src", "cli", "main.ts");
164
172
  if (existsSync(dev))
165
173
  return dev;
166
174
  return join(mmaRoot, "dist", "main.js");
167
175
  }
176
+ /** Ищет корень MMA вверх от каталога (по package.json). */
168
177
  export function findMmaRoot(fromDir) {
169
178
  const candidates = [resolve(fromDir, "..", "..", ".."), resolve(fromDir, "..")];
170
179
  for (const c of candidates) {
@@ -173,6 +182,7 @@ export function findMmaRoot(fromDir) {
173
182
  }
174
183
  return process.cwd();
175
184
  }
185
+ /** Дефолтный раннер: спавнит MMA дочерним процессом с таймаутом и киллом дерева. */
176
186
  export const defaultRunner = (env, cwd, args, timeoutMs) => new Promise((resolvePromise) => {
177
187
  const child = spawn(process.execPath, args, {
178
188
  cwd,