micro-models-agent 2.10.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.
- package/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- 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
|
-
|
|
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 =
|
|
21
|
-
static RESCAN_COOLDOWN_MS =
|
|
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
|
-
/**
|
|
26
|
+
/** Файлы, известные из сводок компакции — переживают сбросы контекста. */
|
|
24
27
|
knownFiles = new Set();
|
|
25
28
|
constructor(baseDir) {
|
|
26
29
|
this.baseDir = baseDir;
|
|
27
30
|
}
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
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
|
-
*
|
|
62
|
-
*
|
|
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
|
-
//
|
|
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"
|
|
77
|
-
//
|
|
78
|
-
//
|
|
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
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* "
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
126
|
-
//
|
|
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
|
-
*
|
|
140
|
-
*
|
|
156
|
+
* Индекс basename → пути, пересборка каждые NAME_CACHE_TTL_MS, чтобы новые
|
|
157
|
+
* файлы (write_file) подхватывались без полного скана на каждый ответ.
|
|
141
158
|
*/
|
|
142
159
|
getNameIndex() {
|
|
143
160
|
const now = Date.now();
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JS/TS
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
* (`isFilePathLike` / `extractFileLikeTokens` / `stripUrls`)
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Здесь же общий фильтр "этот токен вообще файл?"
|
|
8
|
+
* (`isFilePathLike` / `extractFileLikeTokens` / `stripUrls`), чтобы два
|
|
9
|
+
* экстрактора не расходились (было: аудитор считал hostname URL
|
|
10
|
+
* `whom-pointing-jewel-maine.trycloudflare.com` в шаге плана потерянным
|
|
11
|
+
* файлом, а фактическая проверка корректно игнорировала).
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* (`FILE_EXTENSIONS`)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
76
|
-
* (`process.env`, `console.log`, `process.env.NODE`
|
|
77
|
-
* `\b[\w./\\-]+\.[a-z]+`-
|
|
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
|
-
*
|
|
85
|
+
* Расширения, делающие голый токен `word.ext` похожим на путь к файлу.
|
|
86
86
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* (`
|
|
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
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
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
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* `Gismeteo.by`
|
|
252
|
-
* `https://host/repo.git`
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Консистентность через LLM-судью. Спрашивает модель, противоречит ли свежий
|
|
8
|
+
* ответ агента его ранним решениям — вместо хрупкой эвристики совпадения слов,
|
|
9
|
+
* ловившей только дословные развороты.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
//
|
|
70
|
-
//
|
|
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
|
-
//
|
|
85
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
/*
|
|
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,8 +1,7 @@
|
|
|
1
1
|
import { t } from "../../i18n/index";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
}
|