micro-models-agent 2.11.0 → 2.13.1
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 +39 -0
- package/dist/assets/app.js +22 -2
- package/dist/assets/index.html +39 -9
- package/dist/assets/js/dom.js +15 -0
- package/dist/assets/js/feed.js +129 -10
- package/dist/assets/js/sidebar.js +512 -0
- package/dist/assets/js/tabs.js +30 -5
- package/dist/assets/setup.js +267 -8
- package/dist/assets/styles.css +168 -3
- 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 +89 -3
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +89 -3
- 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 +888 -139
- 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,6 +1,6 @@
|
|
|
1
1
|
import { toForwardSlash } from "../../tools/path-utils";
|
|
2
2
|
import { isCodeLanguage } from "./symbols";
|
|
3
|
-
/**
|
|
3
|
+
/** Локфайлы и генерированные манифесты: структурной ценности в карте нет. */
|
|
4
4
|
const NOISE_BASENAMES = new Set([
|
|
5
5
|
"package-lock.json",
|
|
6
6
|
"bun.lock",
|
|
@@ -12,6 +12,7 @@ const NOISE_BASENAMES = new Set([
|
|
|
12
12
|
"composer.lock",
|
|
13
13
|
"poetry.lock",
|
|
14
14
|
]);
|
|
15
|
+
/** True, если файл — локфайловый шум (не попадает в карту). */
|
|
15
16
|
export function isNoiseFile(file) {
|
|
16
17
|
const base = toForwardSlash(file.path).split("/").pop()?.toLowerCase() ?? "";
|
|
17
18
|
return NOISE_BASENAMES.has(base);
|
|
@@ -24,7 +25,7 @@ function isTestFile(path) {
|
|
|
24
25
|
path.includes("__tests__/") ||
|
|
25
26
|
/\.(test|spec)\.[a-z0-9]+$/i.test(path));
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
+
/** Ниже ранг — полезнее для понимания структуры проекта. */
|
|
28
29
|
function rank(file) {
|
|
29
30
|
const path = toForwardSlash(file.path).toLowerCase();
|
|
30
31
|
if (isTestFile(path))
|
|
@@ -33,13 +34,13 @@ function rank(file) {
|
|
|
33
34
|
return 0;
|
|
34
35
|
return 2;
|
|
35
36
|
}
|
|
36
|
-
/**
|
|
37
|
+
/** Верхний сегмент пути (`src/core/x.ts` → `src`; файл корня → его имя). */
|
|
37
38
|
function topDir(path) {
|
|
38
39
|
const normalized = toForwardSlash(path);
|
|
39
40
|
const slash = normalized.indexOf("/");
|
|
40
41
|
return slash >= 0 ? normalized.slice(0, slash) : normalized;
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
+
/** Сколько файлов исходного кода под каждым верхнеуровневым каталогом. */
|
|
43
44
|
function codeCountByTopDir(files) {
|
|
44
45
|
const counts = new Map();
|
|
45
46
|
for (const file of files) {
|
|
@@ -51,19 +52,18 @@ function codeCountByTopDir(files) {
|
|
|
51
52
|
return counts;
|
|
52
53
|
}
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
+
* Выбирает файлы для блока карты проекта в промпте.
|
|
55
56
|
*
|
|
56
|
-
*
|
|
57
|
-
* `files.slice(0, N)`
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* 1.
|
|
61
|
-
* 2.
|
|
62
|
-
* 3.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* does not change between turns and the prefix KV-cache survives.
|
|
57
|
+
* Индекс строится произвольным обходом `readdir`, так что наивный
|
|
58
|
+
* `files.slice(0, N)` может перечислить одни доки, конфиги и тесты, а реальное
|
|
59
|
+
* дерево исходников (`src/`) не показать — модель увидит "карту проекта" без
|
|
60
|
+
* кода. Отбор:
|
|
61
|
+
* 1. выкидывает локфайловый шум,
|
|
62
|
+
* 2. ранжирует исходники выше тестов, тесты выше доков/конфигов,
|
|
63
|
+
* 3. внутри ранга первыми — каталоги с плотнейшим кодом (плотнейший корень
|
|
64
|
+
* почти всегда настоящее дерево исходников — имён каталогов не захардкожено),
|
|
65
|
+
* 4. затем мельче пути, затем алфавит — детерминированно, чтобы блок не
|
|
66
|
+
* менялся между ходами и выживал префиксный KV-кэш.
|
|
67
67
|
*/
|
|
68
68
|
export function selectMapFiles(files, max) {
|
|
69
69
|
const codeByDir = codeCountByTopDir(files);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Модуль индексатора: построение карты проекта, тул project_map, вотчер.
|
|
1
2
|
import { dirname } from "path";
|
|
2
3
|
import { Indexer } from "./walker";
|
|
3
4
|
import { IndexCache } from "./cache";
|
|
@@ -6,20 +7,29 @@ import { buildProjectProfileLine } from "./project-profile";
|
|
|
6
7
|
import { t } from "../../i18n/index";
|
|
7
8
|
import { toForwardSlash } from "../../tools/path-utils";
|
|
8
9
|
import { estimateTokens } from "../../llm/token-counter";
|
|
9
|
-
/**
|
|
10
|
+
/** Макс. файлов в блоке карты проекта (сначала код; см. map-select). */
|
|
10
11
|
const MAP_FILE_LIMIT = 80;
|
|
12
|
+
/** Модуль индексатора: кэш, карта проекта для промпта, тул project_map, вотчер. */
|
|
11
13
|
export class IndexerModule {
|
|
12
14
|
name = "indexer";
|
|
13
15
|
indexer;
|
|
14
16
|
cache;
|
|
15
17
|
baseDir;
|
|
16
18
|
index = null;
|
|
19
|
+
statsCache = null;
|
|
17
20
|
rebuildTimeout = null;
|
|
18
21
|
constructor(opts) {
|
|
19
22
|
this.baseDir = opts.baseDir;
|
|
20
23
|
this.indexer = new Indexer(opts.baseDir);
|
|
21
|
-
|
|
24
|
+
// Привязываем кэш к корню проекта + текущей сигнатуре дерева, чтобы протухший
|
|
25
|
+
// `index-cache.json` (файлы добавлены/удалены/правлены после обхода) не
|
|
26
|
+
// утёк устаревшей картой в системный промпт, а перестроился.
|
|
27
|
+
this.cache = new IndexCache(opts.cacheDir, {
|
|
28
|
+
root: opts.baseDir,
|
|
29
|
+
signature: () => this.indexer.scanSignature(),
|
|
30
|
+
});
|
|
22
31
|
}
|
|
32
|
+
/** Строит индекс (кэш → обход), null при неустранимой ошибке. */
|
|
23
33
|
async buildIndex() {
|
|
24
34
|
if (this.index)
|
|
25
35
|
return this.index;
|
|
@@ -38,11 +48,13 @@ export class IndexerModule {
|
|
|
38
48
|
return null;
|
|
39
49
|
}
|
|
40
50
|
}
|
|
51
|
+
/** Принудительно перестраивает индекс (сброс кэша + обход). */
|
|
41
52
|
async refresh() {
|
|
42
53
|
this.cache.invalidate();
|
|
43
54
|
this.index = null;
|
|
44
55
|
return this.buildIndex();
|
|
45
56
|
}
|
|
57
|
+
/** Ищет файлы по подстроке пути/экспорта (пусто без индекса). */
|
|
46
58
|
find(query) {
|
|
47
59
|
if (!this.index)
|
|
48
60
|
return [];
|
|
@@ -52,7 +64,7 @@ export class IndexerModule {
|
|
|
52
64
|
return path.includes(q) || f.exports.some((e) => e.toLowerCase().includes(q));
|
|
53
65
|
});
|
|
54
66
|
}
|
|
55
|
-
/**
|
|
67
|
+
/** Человеко/модельно-читаемый результат `find` (общий для тула и `mma map`). */
|
|
56
68
|
formatFind(query) {
|
|
57
69
|
const matches = this.find(query);
|
|
58
70
|
if (matches.length === 0) {
|
|
@@ -67,10 +79,11 @@ export class IndexerModule {
|
|
|
67
79
|
.join("\n");
|
|
68
80
|
return t("indexer.find_results", { count: matches.length, results: lines });
|
|
69
81
|
}
|
|
70
|
-
/**
|
|
82
|
+
/** Точный текст карты проекта для системного промпта (null без индекса). */
|
|
71
83
|
getPromptMap() {
|
|
72
84
|
return this.index ? this.formatMap(this.index) : null;
|
|
73
85
|
}
|
|
86
|
+
/** Подписка на изменения файлов с дебаунсом перестройки (best-effort). */
|
|
74
87
|
watch() {
|
|
75
88
|
this.unwatch();
|
|
76
89
|
try {
|
|
@@ -85,9 +98,10 @@ export class IndexerModule {
|
|
|
85
98
|
});
|
|
86
99
|
}
|
|
87
100
|
catch {
|
|
88
|
-
//
|
|
101
|
+
// Вотчинг best-effort; ручной refresh всё равно работает.
|
|
89
102
|
}
|
|
90
103
|
}
|
|
104
|
+
/** Отписывается от вотчера и чистит дебаунс-таймер. */
|
|
91
105
|
unwatch() {
|
|
92
106
|
if (this.rebuildTimeout) {
|
|
93
107
|
clearTimeout(this.rebuildTimeout);
|
|
@@ -95,6 +109,7 @@ export class IndexerModule {
|
|
|
95
109
|
}
|
|
96
110
|
this.indexer.unwatch();
|
|
97
111
|
}
|
|
112
|
+
/** Блок системного промпта с картой проекта (null без индекса). */
|
|
98
113
|
getSystemPromptBlock() {
|
|
99
114
|
if (!this.index)
|
|
100
115
|
return null;
|
|
@@ -107,9 +122,30 @@ export class IndexerModule {
|
|
|
107
122
|
kind: "project-map",
|
|
108
123
|
};
|
|
109
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Компактная статистика карты для сайдбара хоста (`null` до конца обхода).
|
|
127
|
+
* Кэшируется на объект индекса, чтобы поллинг `/api/state` не ререндерил
|
|
128
|
+
* всю карту.
|
|
129
|
+
*/
|
|
130
|
+
stats() {
|
|
131
|
+
if (!this.index)
|
|
132
|
+
return null;
|
|
133
|
+
if (this.statsCache && this.statsCache.index === this.index)
|
|
134
|
+
return this.statsCache.value;
|
|
135
|
+
const symbols = this.index.files.reduce((n, f) => n + f.exports.length, 0);
|
|
136
|
+
const value = {
|
|
137
|
+
files: this.index.files.length,
|
|
138
|
+
symbols,
|
|
139
|
+
tokens: this.estimateTokens(this.formatMap(this.index)),
|
|
140
|
+
};
|
|
141
|
+
this.statsCache = { index: this.index, value };
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
/** Определения тулов модуля (project_map). */
|
|
110
145
|
getToolDefinitions() {
|
|
111
146
|
return [this.createProjectMapTool()];
|
|
112
147
|
}
|
|
148
|
+
/** Плагин модуля: индекс + вотчер на старт сессии, отписка на конец. */
|
|
113
149
|
getPlugin() {
|
|
114
150
|
return {
|
|
115
151
|
name: "indexer",
|
|
@@ -147,18 +183,17 @@ export class IndexerModule {
|
|
|
147
183
|
...(stackLine ? [stackLine] : []),
|
|
148
184
|
`${t("indexer.top_directories")}: ${topDirs}`,
|
|
149
185
|
...(duplicates.length > 0 ? [t("indexer.duplicates", { names: duplicates.join(", ") })] : []),
|
|
150
|
-
`${t("indexer.files")}
|
|
186
|
+
`${t("indexer.files")}:${fileLines.length > 0 ? "" : ` ${t("indexer.empty")}`}`,
|
|
151
187
|
...fileLines,
|
|
152
188
|
more,
|
|
153
189
|
].join("\n");
|
|
154
190
|
}
|
|
155
191
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* to do with the information.
|
|
192
|
+
* Находит файлы с общим basename при разных расширениях в одном каталоге
|
|
193
|
+
* (напр. `ProductCard.js` + `ProductCard.tsx`). Чисто структурный факт дерева —
|
|
194
|
+
* протухший транспилированный близнец может затенять реальный исходник на
|
|
195
|
+
* сборке. Подсветить его — не классификация задач, правило no-keyword-matching
|
|
196
|
+
* не нарушает; что делать с информацией — решает модель.
|
|
162
197
|
*/
|
|
163
198
|
findDuplicateBasenames(result) {
|
|
164
199
|
const byDir = {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Профиль проекта: чтение манифеста (package.json/pyproject/Cargo/go.mod) в одну строку.
|
|
1
2
|
import { readFileSync, existsSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { t } from "../../i18n/index";
|
|
@@ -12,7 +13,7 @@ const MAX_DEPS = 5;
|
|
|
12
13
|
const MAX_DEV_DEPS = 3;
|
|
13
14
|
const MAX_SCRIPTS = 3;
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* Находит первый существующий манифест по приоритету (best-effort, только корень).
|
|
16
17
|
*/
|
|
17
18
|
export function detectManifest(baseDir) {
|
|
18
19
|
for (const manifest of MANIFEST_ORDER) {
|
|
@@ -21,7 +22,7 @@ export function detectManifest(baseDir) {
|
|
|
21
22
|
}
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
|
-
/**
|
|
25
|
+
/** Чистит спеки версий из записи зависимости манифеста (имя без версии). */
|
|
25
26
|
function cleanDependency(entry) {
|
|
26
27
|
let name = entry.trim();
|
|
27
28
|
const eq = name.indexOf("=");
|
|
@@ -135,8 +136,8 @@ function readRequirements(baseDir) {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
139
|
+
* Читает компактный профиль проекта из манифеста (best-effort).
|
|
140
|
+
* Null, если читаемого манифеста нет.
|
|
140
141
|
*/
|
|
141
142
|
export function readProjectProfile(baseDir) {
|
|
142
143
|
const manifest = detectManifest(baseDir);
|
|
@@ -157,7 +158,7 @@ export function readProjectProfile(baseDir) {
|
|
|
157
158
|
return null;
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
|
-
/**
|
|
161
|
+
/** Однострочная стек-нейтральная сводка профиля проекта. */
|
|
161
162
|
export function formatProjectProfile(profile) {
|
|
162
163
|
const parts = [];
|
|
163
164
|
if (profile.deps.length > 0) {
|
|
@@ -174,8 +175,8 @@ export function formatProjectProfile(profile) {
|
|
|
174
175
|
return `[Stack: ${profile.runtime}${name} — ${parts.join(" | ")}]`;
|
|
175
176
|
}
|
|
176
177
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
178
|
+
* Удобно: detect + read + format одним вызовом. Null, если в проекте нет
|
|
179
|
+
* читаемого манифеста.
|
|
179
180
|
*/
|
|
180
181
|
export function buildProjectProfileLine(baseDir) {
|
|
181
182
|
const profile = readProjectProfile(baseDir);
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Реестр языков и извлечение символов для индекса проекта.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `EXT_TO_LANGUAGE`.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Новый язык = одна запись в `RULES` + одно или больше расширений в
|
|
5
|
+
* `EXT_TO_LANGUAGE`. Каждое правило объявляет, код ли это (для ранжирования
|
|
6
|
+
* карты) и как тянуть публичные/экспортные имена из файла. Извлечение
|
|
7
|
+
* regex-based осознанно (без зависимости от парсера): дёшево, гоняется на
|
|
8
|
+
* каждом индексируемом файле, и структуры хватает для полезности карты —
|
|
9
|
+
* полный AST не нужен.
|
|
10
10
|
*/
|
|
11
|
-
/**
|
|
11
|
+
/** Кап на файл: один генерированный файл не раздувает карту. */
|
|
12
12
|
export const MAX_SYMBOLS_PER_FILE = 40;
|
|
13
|
-
/**
|
|
13
|
+
/** Уникальные имена с сохранением порядка. */
|
|
14
14
|
function dedupe(names) {
|
|
15
15
|
const seen = new Set();
|
|
16
16
|
const out = [];
|
|
@@ -22,7 +22,7 @@ function dedupe(names) {
|
|
|
22
22
|
}
|
|
23
23
|
return out;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
25
|
+
/** Собирает группу 1 совпадений по списку глобальных (многострочных) regex'ов. */
|
|
26
26
|
function collect(content, patterns) {
|
|
27
27
|
const names = [];
|
|
28
28
|
for (const re of patterns) {
|
|
@@ -37,7 +37,7 @@ function collect(content, patterns) {
|
|
|
37
37
|
}
|
|
38
38
|
return names;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
40
|
+
/** Имена из клауз `export { a, b as c }` / `export type { X }`. */
|
|
41
41
|
function collectExportClauses(content) {
|
|
42
42
|
const names = [];
|
|
43
43
|
const re = /export\s+(?:type\s+)?\{([^}]*)\}/g;
|
|
@@ -168,22 +168,22 @@ const EXT_TO_LANGUAGE = {
|
|
|
168
168
|
".scss": "css",
|
|
169
169
|
".less": "css",
|
|
170
170
|
};
|
|
171
|
-
/**
|
|
171
|
+
/** Id языка по расширению (lowercase, с точкой), или null если не индексируем. */
|
|
172
172
|
export function languageIdForExt(ext) {
|
|
173
173
|
return EXT_TO_LANGUAGE[ext.toLowerCase()] ?? null;
|
|
174
174
|
}
|
|
175
|
-
/** True
|
|
175
|
+
/** True, если язык — исходный код (влияет на ранжирование карты). */
|
|
176
176
|
export function isCodeLanguage(languageId) {
|
|
177
177
|
return RULES[languageId]?.code ?? false;
|
|
178
178
|
}
|
|
179
|
-
/**
|
|
179
|
+
/** Извлекает публичные/экспортные имена символов из содержимого файла. */
|
|
180
180
|
export function extractSymbols(content, languageId) {
|
|
181
181
|
const rule = RULES[languageId];
|
|
182
182
|
if (!rule)
|
|
183
183
|
return [];
|
|
184
184
|
return dedupe(rule.extract(content)).slice(0, MAX_SYMBOLS_PER_FILE);
|
|
185
185
|
}
|
|
186
|
-
/**
|
|
186
|
+
/** Все индексируемые расширения (для доков/тестов). */
|
|
187
187
|
export function indexedExtensions() {
|
|
188
188
|
return Object.keys(EXT_TO_LANGUAGE).sort();
|
|
189
189
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
// Обход дерева проекта: индекс файлов с символами, вотчер, сигнатура дерева.
|
|
2
|
+
import { readdirSync, readFileSync, statSync, lstatSync, existsSync, watch, } from "fs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
2
4
|
import { join, relative, extname } from "path";
|
|
3
5
|
import { Logger } from "../../logger/app-logger";
|
|
4
6
|
import { languageIdForExt, extractSymbols } from "./symbols";
|
|
5
7
|
import { t } from "../../i18n/index";
|
|
6
8
|
const logger = new Logger("warn", "indexer");
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
9
|
-
* VCS
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Каталоги, где исходников проекта не бывает, но индекс бы утонул:
|
|
11
|
+
* VCS/артефакты сборки, кэши пакетов и виртуалки всех экосистем
|
|
12
|
+
* (Node, Python, Rust, Go/PHP/Ruby, JVM, .NET, Apple, IDE).
|
|
13
|
+
* Хранятся в lowercase — сравнение регистронезависимо (Windows/macOS).
|
|
12
14
|
*/
|
|
13
15
|
const IGNORE_DIRS = new Set([
|
|
14
16
|
// VCS / MMA
|
|
@@ -16,7 +18,13 @@ const IGNORE_DIRS = new Set([
|
|
|
16
18
|
".hg",
|
|
17
19
|
".svn",
|
|
18
20
|
".mma",
|
|
19
|
-
//
|
|
21
|
+
// Каталоги dev/agent/тулинга MMA — не исходники проекта (зеркало .gitignore:
|
|
22
|
+
// карта dev-чекаута не тонет в тестовых песочницах / агентском скратче).
|
|
23
|
+
"_testing",
|
|
24
|
+
".superpowers",
|
|
25
|
+
".opencode",
|
|
26
|
+
".fallow",
|
|
27
|
+
// Node / JS-бандлеры
|
|
20
28
|
"node_modules",
|
|
21
29
|
"dist",
|
|
22
30
|
"build",
|
|
@@ -51,22 +59,45 @@ const IGNORE_DIRS = new Set([
|
|
|
51
59
|
// Apple
|
|
52
60
|
"pods",
|
|
53
61
|
"deriveddata",
|
|
54
|
-
// IDE /
|
|
62
|
+
// IDE / редакторы
|
|
55
63
|
".idea",
|
|
56
64
|
".vscode",
|
|
57
65
|
]);
|
|
58
|
-
/** True
|
|
66
|
+
/** True, если имя каталога игнорируется (регистронезависимо). */
|
|
59
67
|
export function isIgnoredDirName(name) {
|
|
60
68
|
return IGNORE_DIRS.has(name.toLowerCase());
|
|
61
69
|
}
|
|
70
|
+
/** Локфайлы — генерированы, не исходники проекта. */
|
|
71
|
+
const IGNORE_FILE_NAMES = new Set([
|
|
72
|
+
"package-lock.json",
|
|
73
|
+
"bun.lock",
|
|
74
|
+
"bun.lockb",
|
|
75
|
+
"yarn.lock",
|
|
76
|
+
"pnpm-lock.yaml",
|
|
77
|
+
// Вендоренные веб-бандлы (минифицированы; их символы — шум).
|
|
78
|
+
"hljs.js",
|
|
79
|
+
"smd.js",
|
|
80
|
+
]);
|
|
81
|
+
/** Семейства конфигов/бейзлайнов тулинга — не исходники проекта. */
|
|
82
|
+
const IGNORE_FILE_PREFIXES = [".fallow"];
|
|
83
|
+
/** True, если имя файла игнорируется (регистронезависимый префикс/точное совпадение). */
|
|
84
|
+
export function isIgnoredFileName(name) {
|
|
85
|
+
const lower = name.toLowerCase();
|
|
86
|
+
return (IGNORE_FILE_NAMES.has(lower) ||
|
|
87
|
+
lower.endsWith(".min.js") ||
|
|
88
|
+
IGNORE_FILE_PREFIXES.some((p) => lower.startsWith(p)));
|
|
89
|
+
}
|
|
62
90
|
/**
|
|
63
|
-
* True
|
|
64
|
-
*
|
|
65
|
-
* `environment.ts`
|
|
91
|
+
* True, если ЛЮБОЙ СЕГМЕНТ пути — игнорируемый каталог. Посегментно (не
|
|
92
|
+
* подстрока): короткие имена `env`/`out`/`obj` не должны вычёркивать
|
|
93
|
+
* `environment.ts` или `about/`.
|
|
66
94
|
*/
|
|
67
95
|
function isIgnoredPath(filename) {
|
|
68
|
-
|
|
96
|
+
const segments = filename.split(/[\\/]/);
|
|
97
|
+
const base = segments[segments.length - 1] ?? "";
|
|
98
|
+
return segments.some((segment) => isIgnoredDirName(segment)) || isIgnoredFileName(base);
|
|
69
99
|
}
|
|
100
|
+
/** Индексер: обход исходников, вотчер, сигнатура дерева, сводка. */
|
|
70
101
|
export class Indexer {
|
|
71
102
|
baseDir;
|
|
72
103
|
MAX_FILES = 1000;
|
|
@@ -74,22 +105,91 @@ export class Indexer {
|
|
|
74
105
|
constructor(baseDir) {
|
|
75
106
|
this.baseDir = baseDir;
|
|
76
107
|
}
|
|
108
|
+
/** Подписка на изменения дерева (игнорируемые пути не пробрасываются). */
|
|
77
109
|
watch(callback) {
|
|
78
|
-
|
|
110
|
+
const handler = (event, filename) => {
|
|
79
111
|
if (filename && !isIgnoredPath(filename)) {
|
|
80
112
|
callback(event, filename);
|
|
81
113
|
}
|
|
82
|
-
}
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
// Рекурсивный вотч работает на macOS/Windows; на Linux опция игнорируется
|
|
117
|
+
// или бросает в зависимости от сборки Node — ниже фолбэк.
|
|
118
|
+
this.watcher = watch(this.baseDir, { recursive: true }, handler);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
this.watcher = this.watchNonRecursive(handler);
|
|
122
|
+
}
|
|
83
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Linux-фолбэк: `fs.watch(recursive)` только для macOS/Windows, поэтому
|
|
126
|
+
* смотрим каждый неигнорируемый подкаталог отдельно (нерекурсивно).
|
|
127
|
+
* Best-effort: подкаталоги, созданные после `watch()`, требуют ручного refresh.
|
|
128
|
+
*/
|
|
129
|
+
watchNonRecursive(callback) {
|
|
130
|
+
const watchers = [];
|
|
131
|
+
const collectDirs = (dir, out) => {
|
|
132
|
+
let entries;
|
|
133
|
+
try {
|
|
134
|
+
entries = readdirSync(dir);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
out.push(dir);
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const fullPath = join(dir, entry);
|
|
142
|
+
try {
|
|
143
|
+
const lst = lstatSync(fullPath, { throwIfNoEntry: false });
|
|
144
|
+
if (!lst || lst.isSymbolicLink())
|
|
145
|
+
continue;
|
|
146
|
+
if (statSync(fullPath).isDirectory() && !isIgnoredDirName(entry)) {
|
|
147
|
+
collectDirs(fullPath, out);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Нечитаемая запись — пропускаем, идём по остальным.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const dirs = [];
|
|
156
|
+
collectDirs(this.baseDir, dirs);
|
|
157
|
+
for (const dir of dirs) {
|
|
158
|
+
try {
|
|
159
|
+
watchers.push(watch(dir, callback));
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
logger.warn(`watch failed for ${dir}: ${e.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (watchers.length === 0) {
|
|
166
|
+
logger.warn("file watcher unavailable: no directories could be watched");
|
|
167
|
+
}
|
|
168
|
+
return watchers;
|
|
169
|
+
}
|
|
170
|
+
/** Отписывается от вотчера. */
|
|
84
171
|
unwatch() {
|
|
85
172
|
if (this.watcher) {
|
|
86
|
-
this.watcher.
|
|
173
|
+
const all = Array.isArray(this.watcher) ? this.watcher : [this.watcher];
|
|
174
|
+
for (const w of all) {
|
|
175
|
+
try {
|
|
176
|
+
w.close();
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Уже закрыт — нечего делать.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
87
182
|
this.watcher = null;
|
|
88
183
|
}
|
|
89
184
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Обходит каждый индексируемый исходник под `baseDir` и зовёт `visit` с
|
|
187
|
+
* полным путём, относительным путём, id языка и stat. Уважает `MAX_FILES` и
|
|
188
|
+
* `IGNORE_DIRS`; содержимое НЕ читает — `walk()` читает его для извлечения
|
|
189
|
+
* символов, а `scanSignature()` пользуется только stat. Общий, чтобы оба
|
|
190
|
+
* прохода видели ровно то же множество файлов.
|
|
191
|
+
*/
|
|
192
|
+
eachSourceFile(visit) {
|
|
93
193
|
let count = 0;
|
|
94
194
|
const walkDir = (dir) => {
|
|
95
195
|
if (!existsSync(dir))
|
|
@@ -106,9 +206,9 @@ export class Indexer {
|
|
|
106
206
|
return;
|
|
107
207
|
const fullPath = join(dir, entry);
|
|
108
208
|
const relPath = relative(this.baseDir, fullPath);
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
209
|
+
// Изоляция на запись: файл, удалённый (или нечитаемый) между readdir
|
|
210
|
+
// и stat, не должен убивать весь обход. Симлинки пропускаем — могут
|
|
211
|
+
// вести наружу или образовывать циклы каталогов.
|
|
112
212
|
try {
|
|
113
213
|
const lst = lstatSync(fullPath, { throwIfNoEntry: false });
|
|
114
214
|
if (!lst || lst.isSymbolicLink())
|
|
@@ -120,27 +220,57 @@ export class Indexer {
|
|
|
120
220
|
}
|
|
121
221
|
}
|
|
122
222
|
else if (stat.isFile()) {
|
|
123
|
-
|
|
124
|
-
|
|
223
|
+
if (isIgnoredFileName(entry))
|
|
224
|
+
continue;
|
|
225
|
+
const language = languageIdForExt(extname(entry).toLowerCase());
|
|
125
226
|
if (language) {
|
|
126
|
-
|
|
127
|
-
const exports = extractSymbols(content, language);
|
|
128
|
-
files.push({ path: relPath, language, exports, size: stat.size });
|
|
129
|
-
totalSize += stat.size;
|
|
227
|
+
visit(fullPath, relPath, language, stat);
|
|
130
228
|
count++;
|
|
131
229
|
}
|
|
132
230
|
}
|
|
133
231
|
}
|
|
134
232
|
catch (e) {
|
|
135
|
-
//
|
|
233
|
+
// Запись пропала или нечитаема — пропускаем, индексируем остальные.
|
|
136
234
|
logger.warn(t("env.index_skip_unreadable", { path: relPath }), { error: String(e) });
|
|
137
|
-
continue;
|
|
138
235
|
}
|
|
139
236
|
}
|
|
140
237
|
};
|
|
141
238
|
walkDir(this.baseDir);
|
|
239
|
+
}
|
|
240
|
+
/** Полный обход: читает файлы, извлекает символы. Нечитаемые пропускает. */
|
|
241
|
+
async walk() {
|
|
242
|
+
const files = [];
|
|
243
|
+
let totalSize = 0;
|
|
244
|
+
this.eachSourceFile((fullPath, relPath, language, stat) => {
|
|
245
|
+
try {
|
|
246
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
247
|
+
const exports = extractSymbols(content, language);
|
|
248
|
+
files.push({ path: relPath, language, exports, size: stat.size });
|
|
249
|
+
totalSize += stat.size;
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
// Чтение упало после успешного stat — пропускаем файл, берём остальные.
|
|
253
|
+
logger.warn(t("env.index_skip_unreadable", { path: relPath }), { error: String(e) });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
142
256
|
return { files, totalSize };
|
|
143
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Дешёвый бесконтентный отпечаток индексируемого дерева: отсортированные
|
|
260
|
+
* `path\0size\0mtimeMs`, хешированные SHA-1. `IndexCache` хранит его и
|
|
261
|
+
* пересчитывает на load, отбраковывая протухший `index-cache.json` (файл
|
|
262
|
+
* добавлен/удалён/правлен после обхода — сигнатура меняется). Только stat,
|
|
263
|
+
* без чтения файлов — куда дешевле полного обхода.
|
|
264
|
+
*/
|
|
265
|
+
scanSignature() {
|
|
266
|
+
const parts = [];
|
|
267
|
+
this.eachSourceFile((_fullPath, relPath, _language, stat) => {
|
|
268
|
+
parts.push(`${relPath}\u0000${stat.size}\u0000${Math.floor(stat.mtimeMs)}`);
|
|
269
|
+
});
|
|
270
|
+
parts.sort();
|
|
271
|
+
return createHash("sha1").update(parts.join("\n")).digest("hex");
|
|
272
|
+
}
|
|
273
|
+
/** Однострочная сводка индекса (файлы, размер, языки). */
|
|
144
274
|
summarize(result) {
|
|
145
275
|
const byLang = {};
|
|
146
276
|
for (const f of result.files) {
|