micro-models-agent 0.63.3 → 1.1.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 +148 -1
- package/dist/cli/cache-line.js +30 -0
- package/dist/cli/command-suggest.js +38 -0
- package/dist/cli/commands.js +285 -60
- package/dist/cli/completer.js +16 -16
- package/dist/cli/json-payload.js +32 -0
- package/dist/cli/main.js +165 -77
- package/dist/cli/plugin-commands.js +5 -4
- package/dist/cli/relaunch.js +37 -0
- package/dist/cli/repl-commands.js +441 -307
- package/dist/cli/repl.js +360 -83
- package/dist/cli/run-result.js +12 -6
- package/dist/cli/security-commands.js +64 -60
- package/dist/cli/setup-order.js +57 -0
- package/dist/cli/setup-prompt.js +49 -0
- package/dist/cli/setup.js +52 -48
- package/dist/config/budget.js +48 -0
- package/dist/config/config.js +132 -70
- package/dist/config/defaults.js +37 -11
- package/dist/config/domains.js +9 -50
- package/dist/config/utils.js +56 -0
- package/dist/core/agent/audit-gate.js +49 -0
- package/dist/core/agent/compaction.js +89 -0
- package/dist/core/agent/constants.js +61 -0
- package/dist/core/agent/context-renderer.js +40 -0
- package/dist/core/agent/hallucination-gate.js +87 -0
- package/dist/core/agent/loop-state.js +53 -0
- package/dist/core/agent/prefix-monitor.js +101 -0
- package/dist/core/agent/reasoning-resolver.js +56 -0
- package/dist/core/agent/token-tracker.js +96 -0
- package/dist/core/agent/tool-batch.js +237 -0
- package/dist/core/agent/tool-output.js +62 -0
- package/dist/core/agent-moe.js +214 -69
- package/dist/core/agent.js +506 -546
- package/dist/core/bootstrap.js +297 -98
- package/dist/core/crash-handler.js +2 -1
- package/dist/core/prompt-builder.js +3 -0
- package/dist/core/prompt-overflow.js +307 -0
- package/dist/core/session-logger.js +34 -2
- package/dist/i18n/en.json +7 -4
- package/dist/i18n/ru.json +7 -4
- package/dist/index.js +5 -1
- package/dist/llm/cache-usage.js +76 -0
- package/dist/llm/image-utils.js +20 -16
- package/dist/llm/llm-errors.js +41 -0
- package/dist/llm/model-loader.js +30 -0
- package/dist/llm/openai-compat.js +287 -101
- package/dist/llm/orchestrator.js +140 -68
- package/dist/llm/provider-budget.js +68 -0
- package/dist/llm/provider.js +0 -1
- package/dist/llm/stream-state.js +26 -0
- package/dist/llm/token-counter.js +28 -0
- package/dist/logger/app-logger.js +12 -15
- package/dist/main.js +1606 -800
- package/dist/migration/detect.js +3 -1
- package/dist/modules/browser/actions.js +0 -3
- package/dist/modules/browser/bridge-client.js +2 -0
- package/dist/modules/browser/driver.js +46 -4
- package/dist/modules/certification/cli.js +85 -42
- package/dist/modules/certification/loader.js +15 -1
- package/dist/modules/certification/manifest.js +126 -15
- package/dist/modules/certification/runner.js +4 -26
- package/dist/modules/certification/scenarios.js +184 -5
- package/dist/modules/certification/syntax-scenarios.js +51 -0
- package/dist/modules/context/chunk-query.js +25 -5
- package/dist/modules/context/fact-extractor.js +6 -2
- package/dist/modules/context/manager.js +23 -7
- package/dist/modules/execution/audit-runners.js +7 -1
- package/dist/modules/execution/auditor.js +3 -3
- package/dist/modules/execution/execution-plugin.js +22 -15
- package/dist/modules/execution/input-from.js +46 -0
- package/dist/modules/execution/module.js +107 -18
- package/dist/modules/execution/moe-executor.js +166 -54
- package/dist/modules/execution/plan-actions.js +524 -0
- package/dist/modules/execution/plan-steps.js +23 -0
- package/dist/modules/execution/plan-store.js +15 -3
- package/dist/modules/execution/plan-tool.js +6 -488
- package/dist/modules/execution/plan-validator.js +24 -0
- package/dist/modules/execution/stuck-detector.js +3 -18
- package/dist/modules/execution/tracker.js +14 -5
- package/dist/modules/execution/transient-error.js +30 -0
- package/dist/modules/execution/verifier.js +94 -7
- package/dist/modules/execution/windows-commands.js +11 -0
- package/dist/modules/hallucination/confidence.js +36 -23
- package/dist/modules/hallucination/consistency.js +3 -0
- package/dist/modules/hallucination/detector.js +8 -3
- package/dist/modules/hallucination/factual.js +26 -7
- package/dist/modules/hallucination/llm-judge.js +12 -2
- package/dist/modules/indexer/map-command.js +35 -0
- package/dist/modules/indexer/map-select.js +87 -0
- package/dist/modules/indexer/module.js +34 -22
- package/dist/modules/indexer/symbols.js +189 -0
- package/dist/modules/indexer/walker.js +96 -42
- package/dist/modules/lsp/check-tool.js +2 -1
- package/dist/modules/lsp/client.js +49 -32
- package/dist/modules/lsp/config.js +55 -2
- package/dist/modules/lsp/module.js +38 -5
- package/dist/modules/lsp/probe.js +4 -3
- package/dist/modules/lsp/project-root.js +41 -1
- package/dist/modules/lsp/startup-check.js +12 -4
- package/dist/modules/mcp/client.js +153 -104
- package/dist/modules/mcp/module.js +165 -41
- package/dist/modules/memory/module.js +4 -3
- package/dist/modules/plugins/builtin/lint-on-write.js +36 -6
- package/dist/modules/plugins/manager.js +47 -84
- package/dist/modules/pricing/index.js +17 -7
- package/dist/modules/pricing/prices.js +30 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +56 -0
- package/dist/modules/processes/registry.js +2 -54
- package/dist/modules/providers/cache.js +23 -0
- package/dist/modules/providers/factory.js +28 -0
- package/dist/modules/providers/fallback.js +7 -5
- package/dist/modules/providers/health.js +2 -1
- package/dist/modules/providers/index.js +1 -0
- package/dist/modules/providers/manager.js +17 -2
- package/dist/modules/providers/presets.js +79 -6
- package/dist/modules/reasoning/policy.js +40 -0
- package/dist/modules/reasoning/probe.js +111 -0
- package/dist/modules/security/audit-notifier.js +42 -27
- package/dist/modules/security/command-validator.js +25 -20
- package/dist/modules/security/encryption.js +6 -12
- package/dist/modules/security/network-validator.js +76 -5
- package/dist/modules/security/path-validator.js +77 -34
- package/dist/modules/security/rate-limiter.js +11 -0
- package/dist/modules/security/security-policies.js +1 -1
- package/dist/modules/security/session-encryption.js +13 -2
- package/dist/modules/security/session-isolation.js +2 -9
- package/dist/modules/session/manager.js +11 -0
- package/dist/modules/session/module.js +11 -3
- package/dist/modules/session/store.js +41 -5
- package/dist/modules/skills/loader.js +7 -1
- package/dist/modules/skills/module.js +2 -1
- package/dist/modules/updater/changelog-reader.js +94 -0
- package/dist/modules/updater/dev-detect.js +17 -0
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +14 -3
- package/dist/output/bus.js +32 -0
- package/dist/output/channel.js +233 -0
- package/dist/output/format.js +14 -0
- package/dist/output/index.js +7 -0
- package/dist/output/json-sink.js +22 -0
- package/dist/output/machine.js +8 -0
- package/dist/output/session-sink.js +27 -0
- package/dist/output/types.js +1 -0
- package/dist/tools/approve.js +6 -2
- package/dist/tools/attach-image.js +11 -11
- package/dist/tools/auto-fixer.js +198 -0
- package/dist/tools/bash.js +142 -89
- package/dist/tools/chunk-query.js +10 -6
- package/dist/tools/download-file.js +1 -1
- package/dist/tools/edit-file.js +20 -2
- package/dist/tools/executor.js +54 -9
- package/dist/tools/glob-tool.js +7 -0
- package/dist/tools/grep-tool.js +15 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/list-dir.js +3 -1
- package/dist/tools/load-skill.js +2 -1
- package/dist/tools/mcp-call.js +1 -1
- package/dist/tools/move-file.js +5 -4
- package/dist/tools/path-utils.js +7 -0
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/prompt-io.js +28 -0
- package/dist/tools/question.js +12 -12
- package/dist/tools/scope-request.js +91 -0
- package/dist/tools/session-info.js +44 -0
- package/dist/tools/set-thinking.js +71 -0
- package/dist/tools/subagent.js +50 -9
- package/dist/tools/syntax-validator.js +177 -0
- package/dist/tools/user-input.js +16 -9
- package/dist/tools/write-file.js +17 -1
- package/dist/ui/diff.js +10 -0
- package/dist/ui/line-editor.js +179 -26
- package/dist/ui/line-math.js +20 -3
- package/dist/ui/md-formatter.js +100 -10
- package/dist/ui/output.js +5 -4
- package/dist/ui/plan-view.js +2 -7
- package/dist/ui/renderer.js +89 -85
- package/dist/ui/spinner.js +14 -4
- package/dist/utils/error.js +4 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/retry.js +17 -0
- package/dist/utils/sleep.js +23 -0
- package/dist/utils/truncate.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language registry and symbol extraction for the project index.
|
|
3
|
+
*
|
|
4
|
+
* Adding a language = one entry in `RULES` + one or more extensions in
|
|
5
|
+
* `EXT_TO_LANGUAGE`. Each rule declares whether the language is source code
|
|
6
|
+
* (used for map ranking) and how to pull public/exported symbol names out of
|
|
7
|
+
* a file. Extraction is regex-based on purpose (no parser dependency): it is
|
|
8
|
+
* cheap, runs on every indexed file, and only needs enough structure to make
|
|
9
|
+
* the project map useful — not a full AST.
|
|
10
|
+
*/
|
|
11
|
+
/** Cap per file so a single generated file cannot blow up the map. */
|
|
12
|
+
export const MAX_SYMBOLS_PER_FILE = 40;
|
|
13
|
+
/** Unique preserved-order names. */
|
|
14
|
+
function dedupe(names) {
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const name of names) {
|
|
18
|
+
if (name && !seen.has(name)) {
|
|
19
|
+
seen.add(name);
|
|
20
|
+
out.push(name);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
/** Collect captured group 1 from a list of global (multiline) regexes. */
|
|
26
|
+
function collect(content, patterns) {
|
|
27
|
+
const names = [];
|
|
28
|
+
for (const re of patterns) {
|
|
29
|
+
re.lastIndex = 0;
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = re.exec(content)) !== null) {
|
|
32
|
+
if (match[1])
|
|
33
|
+
names.push(match[1]);
|
|
34
|
+
if (match.index === re.lastIndex)
|
|
35
|
+
re.lastIndex++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return names;
|
|
39
|
+
}
|
|
40
|
+
/** Names from `export { a, b as c }` / `export type { X }` clauses. */
|
|
41
|
+
function collectExportClauses(content) {
|
|
42
|
+
const names = [];
|
|
43
|
+
const re = /export\s+(?:type\s+)?\{([^}]*)\}/g;
|
|
44
|
+
let match;
|
|
45
|
+
while ((match = re.exec(content)) !== null) {
|
|
46
|
+
for (const part of match[1].split(",")) {
|
|
47
|
+
const cleaned = part.trim().replace(/^type\s+/, "");
|
|
48
|
+
if (!cleaned || cleaned.startsWith("//"))
|
|
49
|
+
continue;
|
|
50
|
+
const alias = cleaned.split(/\s+as\s+/);
|
|
51
|
+
const exported = (alias.length > 1 ? alias[1] : alias[0]).trim();
|
|
52
|
+
if (/^\w+$/.test(exported))
|
|
53
|
+
names.push(exported);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return names;
|
|
57
|
+
}
|
|
58
|
+
const TS_JS_PATTERNS = [
|
|
59
|
+
/^export\s+(?:default\s+)?(?:declare\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm,
|
|
60
|
+
/^export\s+default\s+(?:class|function)\s+(\w+)/gm,
|
|
61
|
+
];
|
|
62
|
+
function tsLike(content) {
|
|
63
|
+
return dedupe([...collect(content, TS_JS_PATTERNS), ...collectExportClauses(content)]);
|
|
64
|
+
}
|
|
65
|
+
const PYTHON_PATTERNS = [/^(?:async\s+)?def\s+(\w+)/gm, /^class\s+(\w+)/gm];
|
|
66
|
+
const RUST_PATTERNS = [
|
|
67
|
+
/^\s*pub(?:\([^)]*\))?\s+(?:async\s+)?(?:unsafe\s+)?(?:fn|struct|enum|trait|const|static|type|mod)\s+(\w+)/gm,
|
|
68
|
+
/macro_rules!\s+(\w+)/g,
|
|
69
|
+
];
|
|
70
|
+
const GO_PATTERNS = [
|
|
71
|
+
/^func\s+(\w+)\s*[([]/gm,
|
|
72
|
+
/^func\s+\([^)]*\)\s+(\w+)\s*[([]/gm,
|
|
73
|
+
/^type\s+(\w+)/gm,
|
|
74
|
+
/^(?:var|const)\s+(\w+)/gm,
|
|
75
|
+
];
|
|
76
|
+
const JAVA_PATTERNS = [
|
|
77
|
+
/^\s*(?:(?:public|protected|private|final|abstract|static|sealed|non-sealed|strictfp)\s+)*(?:class|interface|enum|record)\s+(\w+)/gm,
|
|
78
|
+
];
|
|
79
|
+
const KOTLIN_PATTERNS = [
|
|
80
|
+
/^\s*(?:(?:public|internal|private|protected|open|abstract|sealed|data|enum|annotation|value|inline)\s+)*(?:class|interface|object)\s+(\w+)/gm,
|
|
81
|
+
/^\s*(?:(?:public|internal|private|protected|open|override|suspend|inline|operator|infix|tailrec|external)\s+)*fun\s+(\w+)/gm,
|
|
82
|
+
];
|
|
83
|
+
const CSHARP_PATTERNS = [
|
|
84
|
+
/^\s*(?:(?:public|internal|private|protected|static|sealed|abstract|partial|readonly|unsafe|new|record)\s+)*(?:class|interface|struct|enum|record)\s+(\w+)/gm,
|
|
85
|
+
];
|
|
86
|
+
const RUBY_PATTERNS = [/^\s*(?:def|class|module)\s+([A-Za-z_]\w*[?!]?)/gm];
|
|
87
|
+
const PHP_PATTERNS = [/\b(?:function|class|interface|trait|enum)\s+(\w+)/g];
|
|
88
|
+
const SWIFT_PATTERNS = [/\b(?:func|class|struct|enum|protocol|extension)\s+(\w+)/g];
|
|
89
|
+
const SCALA_PATTERNS = [
|
|
90
|
+
/^\s*(?:(?:private|protected|final|abstract|sealed|implicit|lazy|override)\s+)*(?:def|class|object|trait)\s+(\w+)/gm,
|
|
91
|
+
];
|
|
92
|
+
const C_CPP_PATTERNS = [
|
|
93
|
+
/^\s*(?:typedef\s+)?(?:class|struct|enum|union|namespace)\s+(\w+)/gm,
|
|
94
|
+
/\}\s*(\w+)\s*;/g,
|
|
95
|
+
];
|
|
96
|
+
const SHELL_PATTERNS = [/^\s*(?:function\s+)?(\w+)\s*\(\s*\)\s*\{/gm];
|
|
97
|
+
const LUA_PATTERNS = [/^\s*(?:local\s+)?function\s+([\w.:]+)/gm];
|
|
98
|
+
const NO_SYMBOLS = () => [];
|
|
99
|
+
const RULES = {
|
|
100
|
+
typescript: { code: true, extract: tsLike },
|
|
101
|
+
"typescript-react": { code: true, extract: tsLike },
|
|
102
|
+
javascript: { code: true, extract: tsLike },
|
|
103
|
+
"javascript-react": { code: true, extract: tsLike },
|
|
104
|
+
python: { code: true, extract: (c) => collect(c, PYTHON_PATTERNS) },
|
|
105
|
+
rust: { code: true, extract: (c) => collect(c, RUST_PATTERNS) },
|
|
106
|
+
go: { code: true, extract: (c) => collect(c, GO_PATTERNS) },
|
|
107
|
+
java: { code: true, extract: (c) => collect(c, JAVA_PATTERNS) },
|
|
108
|
+
kotlin: { code: true, extract: (c) => collect(c, KOTLIN_PATTERNS) },
|
|
109
|
+
csharp: { code: true, extract: (c) => collect(c, CSHARP_PATTERNS) },
|
|
110
|
+
ruby: { code: true, extract: (c) => collect(c, RUBY_PATTERNS) },
|
|
111
|
+
php: { code: true, extract: (c) => collect(c, PHP_PATTERNS) },
|
|
112
|
+
swift: { code: true, extract: (c) => collect(c, SWIFT_PATTERNS) },
|
|
113
|
+
scala: { code: true, extract: (c) => collect(c, SCALA_PATTERNS) },
|
|
114
|
+
c: { code: true, extract: (c) => collect(c, C_CPP_PATTERNS) },
|
|
115
|
+
cpp: { code: true, extract: (c) => collect(c, C_CPP_PATTERNS) },
|
|
116
|
+
shell: { code: true, extract: (c) => collect(c, SHELL_PATTERNS) },
|
|
117
|
+
lua: { code: true, extract: (c) => collect(c, LUA_PATTERNS) },
|
|
118
|
+
json: { code: false, extract: NO_SYMBOLS },
|
|
119
|
+
markdown: { code: false, extract: NO_SYMBOLS },
|
|
120
|
+
yaml: { code: false, extract: NO_SYMBOLS },
|
|
121
|
+
toml: { code: false, extract: NO_SYMBOLS },
|
|
122
|
+
xml: { code: false, extract: NO_SYMBOLS },
|
|
123
|
+
html: { code: false, extract: NO_SYMBOLS },
|
|
124
|
+
css: { code: false, extract: NO_SYMBOLS },
|
|
125
|
+
};
|
|
126
|
+
const EXT_TO_LANGUAGE = {
|
|
127
|
+
".ts": "typescript",
|
|
128
|
+
".mts": "typescript",
|
|
129
|
+
".cts": "typescript",
|
|
130
|
+
".tsx": "typescript-react",
|
|
131
|
+
".js": "javascript",
|
|
132
|
+
".mjs": "javascript",
|
|
133
|
+
".cjs": "javascript",
|
|
134
|
+
".jsx": "javascript-react",
|
|
135
|
+
".py": "python",
|
|
136
|
+
".pyi": "python",
|
|
137
|
+
".rs": "rust",
|
|
138
|
+
".go": "go",
|
|
139
|
+
".java": "java",
|
|
140
|
+
".kt": "kotlin",
|
|
141
|
+
".kts": "kotlin",
|
|
142
|
+
".cs": "csharp",
|
|
143
|
+
".rb": "ruby",
|
|
144
|
+
".php": "php",
|
|
145
|
+
".swift": "swift",
|
|
146
|
+
".scala": "scala",
|
|
147
|
+
".c": "c",
|
|
148
|
+
".h": "c",
|
|
149
|
+
".cpp": "cpp",
|
|
150
|
+
".cc": "cpp",
|
|
151
|
+
".cxx": "cpp",
|
|
152
|
+
".hpp": "cpp",
|
|
153
|
+
".hxx": "cpp",
|
|
154
|
+
".sh": "shell",
|
|
155
|
+
".bash": "shell",
|
|
156
|
+
".zsh": "shell",
|
|
157
|
+
".lua": "lua",
|
|
158
|
+
".json": "json",
|
|
159
|
+
".md": "markdown",
|
|
160
|
+
".markdown": "markdown",
|
|
161
|
+
".yaml": "yaml",
|
|
162
|
+
".yml": "yaml",
|
|
163
|
+
".toml": "toml",
|
|
164
|
+
".xml": "xml",
|
|
165
|
+
".html": "html",
|
|
166
|
+
".htm": "html",
|
|
167
|
+
".css": "css",
|
|
168
|
+
".scss": "css",
|
|
169
|
+
".less": "css",
|
|
170
|
+
};
|
|
171
|
+
/** Language id for a file extension (lowercase, with dot), or null if not indexed. */
|
|
172
|
+
export function languageIdForExt(ext) {
|
|
173
|
+
return EXT_TO_LANGUAGE[ext.toLowerCase()] ?? null;
|
|
174
|
+
}
|
|
175
|
+
/** True when the language is source code (affects project-map ranking). */
|
|
176
|
+
export function isCodeLanguage(languageId) {
|
|
177
|
+
return RULES[languageId]?.code ?? false;
|
|
178
|
+
}
|
|
179
|
+
/** Extract public/exported symbol names from file content. */
|
|
180
|
+
export function extractSymbols(content, languageId) {
|
|
181
|
+
const rule = RULES[languageId];
|
|
182
|
+
if (!rule)
|
|
183
|
+
return [];
|
|
184
|
+
return dedupe(rule.extract(content)).slice(0, MAX_SYMBOLS_PER_FILE);
|
|
185
|
+
}
|
|
186
|
+
/** All indexed extensions (for docs/tests). */
|
|
187
|
+
export function indexedExtensions() {
|
|
188
|
+
return Object.keys(EXT_TO_LANGUAGE).sort();
|
|
189
|
+
}
|
|
@@ -1,19 +1,72 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, statSync, existsSync, watch } from "fs";
|
|
1
|
+
import { readdirSync, readFileSync, statSync, lstatSync, existsSync, watch } from "fs";
|
|
2
2
|
import { join, relative, extname } from "path";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
import { Logger } from "../../logger/app-logger";
|
|
4
|
+
import { languageIdForExt, extractSymbols } from "./symbols";
|
|
5
|
+
import { t } from "../../i18n/index";
|
|
6
|
+
const logger = new Logger("warn", "indexer");
|
|
7
|
+
/**
|
|
8
|
+
* Directories that never hold project source but would flood the index:
|
|
9
|
+
* VCS/build output, package caches and virtual environments across
|
|
10
|
+
* ecosystems (Node, Python, Rust, Go/PHP/Ruby, JVM, .NET, Apple, IDE).
|
|
11
|
+
* Stored lowercase — matching is case-insensitive (Windows/macOS).
|
|
12
|
+
*/
|
|
13
|
+
const IGNORE_DIRS = new Set([
|
|
14
|
+
// VCS / MMA
|
|
15
|
+
".git",
|
|
16
|
+
".hg",
|
|
17
|
+
".svn",
|
|
18
|
+
".mma",
|
|
19
|
+
// Node / JS bundlers
|
|
20
|
+
"node_modules",
|
|
21
|
+
"dist",
|
|
22
|
+
"build",
|
|
23
|
+
"coverage",
|
|
24
|
+
".next",
|
|
25
|
+
".nuxt",
|
|
26
|
+
".svelte-kit",
|
|
27
|
+
".turbo",
|
|
28
|
+
".parcel-cache",
|
|
29
|
+
".cache",
|
|
30
|
+
// Python
|
|
31
|
+
"venv",
|
|
32
|
+
".venv",
|
|
33
|
+
"env",
|
|
34
|
+
"virtualenv",
|
|
35
|
+
"__pycache__",
|
|
36
|
+
".pytest_cache",
|
|
37
|
+
".mypy_cache",
|
|
38
|
+
".ruff_cache",
|
|
39
|
+
".tox",
|
|
40
|
+
"site-packages",
|
|
41
|
+
// Rust
|
|
42
|
+
"target",
|
|
43
|
+
// Go / PHP / Ruby
|
|
44
|
+
"vendor",
|
|
45
|
+
".bundle",
|
|
46
|
+
// JVM
|
|
47
|
+
".gradle",
|
|
48
|
+
"out",
|
|
49
|
+
// .NET
|
|
50
|
+
"obj",
|
|
51
|
+
// Apple
|
|
52
|
+
"pods",
|
|
53
|
+
"deriveddata",
|
|
54
|
+
// IDE / editor
|
|
55
|
+
".idea",
|
|
56
|
+
".vscode",
|
|
57
|
+
]);
|
|
58
|
+
/** True when a directory name is ignored (case-insensitive). */
|
|
59
|
+
export function isIgnoredDirName(name) {
|
|
60
|
+
return IGNORE_DIRS.has(name.toLowerCase());
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* True when any path SEGMENT is an ignored directory. Segment-based (not a
|
|
64
|
+
* substring) so short names like `env`/`out`/`obj` do not black out
|
|
65
|
+
* `environment.ts` or `about/`.
|
|
66
|
+
*/
|
|
67
|
+
function isIgnoredPath(filename) {
|
|
68
|
+
return filename.split(/[\\/]/).some((segment) => isIgnoredDirName(segment));
|
|
69
|
+
}
|
|
17
70
|
export class Indexer {
|
|
18
71
|
baseDir;
|
|
19
72
|
MAX_FILES = 1000;
|
|
@@ -23,7 +76,7 @@ export class Indexer {
|
|
|
23
76
|
}
|
|
24
77
|
watch(callback) {
|
|
25
78
|
this.watcher = watch(this.baseDir, { recursive: true }, (event, filename) => {
|
|
26
|
-
if (filename && !
|
|
79
|
+
if (filename && !isIgnoredPath(filename)) {
|
|
27
80
|
callback(event, filename);
|
|
28
81
|
}
|
|
29
82
|
});
|
|
@@ -53,40 +106,41 @@ export class Indexer {
|
|
|
53
106
|
return;
|
|
54
107
|
const fullPath = join(dir, entry);
|
|
55
108
|
const relPath = relative(this.baseDir, fullPath);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
// Per-entry isolation: a file deleted (or unreadable) between readdir
|
|
110
|
+
// and stat must not kill the whole walk. Symlinks are skipped — they
|
|
111
|
+
// can point outside the project or create directory cycles.
|
|
112
|
+
try {
|
|
113
|
+
const lst = lstatSync(fullPath, { throwIfNoEntry: false });
|
|
114
|
+
if (!lst || lst.isSymbolicLink())
|
|
115
|
+
continue;
|
|
116
|
+
const stat = statSync(fullPath);
|
|
117
|
+
if (stat.isDirectory()) {
|
|
118
|
+
if (!isIgnoredDirName(entry)) {
|
|
119
|
+
walkDir(fullPath);
|
|
120
|
+
}
|
|
60
121
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
122
|
+
else if (stat.isFile()) {
|
|
123
|
+
const ext = extname(entry).toLowerCase();
|
|
124
|
+
const language = languageIdForExt(ext);
|
|
125
|
+
if (language) {
|
|
126
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
127
|
+
const exports = extractSymbols(content, language);
|
|
128
|
+
files.push({ path: relPath, language, exports, size: stat.size });
|
|
129
|
+
totalSize += stat.size;
|
|
130
|
+
count++;
|
|
131
|
+
}
|
|
71
132
|
}
|
|
72
133
|
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
// Entry vanished or is unreadable — skip it, keep indexing the rest.
|
|
136
|
+
logger.warn(t("env.index_skip_unreadable", { path: relPath }), { error: String(e) });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
73
139
|
}
|
|
74
140
|
};
|
|
75
141
|
walkDir(this.baseDir);
|
|
76
142
|
return { files, totalSize };
|
|
77
143
|
}
|
|
78
|
-
extractExports(content, language) {
|
|
79
|
-
if (language === "typescript" || language === "javascript") {
|
|
80
|
-
const exports = [];
|
|
81
|
-
const exportRegex = /export\s+(?:const|function|class|interface|type|enum|default\s+(?:class|function))\s+(\w+)/g;
|
|
82
|
-
let match;
|
|
83
|
-
while ((match = exportRegex.exec(content)) !== null) {
|
|
84
|
-
exports.push(match[1]);
|
|
85
|
-
}
|
|
86
|
-
return exports;
|
|
87
|
-
}
|
|
88
|
-
return [];
|
|
89
|
-
}
|
|
90
144
|
summarize(result) {
|
|
91
145
|
const byLang = {};
|
|
92
146
|
for (const f of result.files) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, stat } from "fs/promises";
|
|
2
2
|
import { resolve, relative } from "path";
|
|
3
3
|
import { getServerForFile } from "./config";
|
|
4
|
+
import { toForwardSlash } from "../../tools/path-utils";
|
|
4
5
|
/** lsp_check directory mode: walk depth and per-call file cap. */
|
|
5
6
|
export const MAX_LSP_CHECK_FILES = 15;
|
|
6
7
|
const MAX_LSP_WALK_DEPTH = 4;
|
|
@@ -48,7 +49,7 @@ export function formatCheckDiagnostics(items, baseDir) {
|
|
|
48
49
|
return items
|
|
49
50
|
.slice(0, 40)
|
|
50
51
|
.map(({ file, diag }) => {
|
|
51
|
-
const rel = relative(baseDir, file)
|
|
52
|
+
const rel = toForwardSlash(relative(baseDir, file));
|
|
52
53
|
const line = diag.range.start.line + 1;
|
|
53
54
|
const col = diag.range.start.character + 1;
|
|
54
55
|
const sev = severityLabels[diag.severity] ?? "unknown";
|
|
@@ -3,6 +3,8 @@ import { resolve } from "path";
|
|
|
3
3
|
import { platform } from "os";
|
|
4
4
|
import { resolveSpawnCommand, buildWinCommandLine } from "./command";
|
|
5
5
|
import { killTree } from "../processes/registry";
|
|
6
|
+
import { toForwardSlash } from "../../tools/path-utils";
|
|
7
|
+
import { errMsg } from "../../utils";
|
|
6
8
|
/**
|
|
7
9
|
* Logger adapter that writes LSP diagnostics to app.jsonl only — never to
|
|
8
10
|
* console/terminal. LSP warnings are noisy (timeout on first run, npx
|
|
@@ -36,7 +38,7 @@ export class LspClient {
|
|
|
36
38
|
process = null;
|
|
37
39
|
requestId = 0;
|
|
38
40
|
pending = new Map();
|
|
39
|
-
buffer =
|
|
41
|
+
buffer = Buffer.alloc(0);
|
|
40
42
|
contentLength = -1;
|
|
41
43
|
diagnostics = [];
|
|
42
44
|
diagnosticsResolve = null;
|
|
@@ -103,7 +105,7 @@ export class LspClient {
|
|
|
103
105
|
await this.sendRequest("initialize", initParams, timeout);
|
|
104
106
|
}
|
|
105
107
|
catch (e) {
|
|
106
|
-
const msg =
|
|
108
|
+
const msg = errMsg(e);
|
|
107
109
|
this.logger?.warn(`LSP initialize failed (attempt 1): ${msg}`, {
|
|
108
110
|
command: config.command,
|
|
109
111
|
projectRoot,
|
|
@@ -117,7 +119,7 @@ export class LspClient {
|
|
|
117
119
|
await this.sendRequest("initialize", initParams, timeout);
|
|
118
120
|
}
|
|
119
121
|
catch (retryErr) {
|
|
120
|
-
const retryMsg =
|
|
122
|
+
const retryMsg = errMsg(retryErr);
|
|
121
123
|
this.logger?.error(`LSP initialize failed (attempt 2, giving up): ${retryMsg}`, {
|
|
122
124
|
command: config.command,
|
|
123
125
|
projectRoot,
|
|
@@ -167,6 +169,11 @@ export class LspClient {
|
|
|
167
169
|
stdio: ["pipe", "pipe", "pipe"],
|
|
168
170
|
env: { ...process.env, ...config.env },
|
|
169
171
|
cwd: projectRoot,
|
|
172
|
+
// POSIX: make the child a process-group leader so killTree()'s
|
|
173
|
+
// `kill(-pid)` reaches the whole chain (npx → node → tsserver).
|
|
174
|
+
// Without it the group kill fails with ESRCH and grandchildren
|
|
175
|
+
// survive as orphans. Windows relies on taskkill /T instead.
|
|
176
|
+
detached: !isWin,
|
|
170
177
|
};
|
|
171
178
|
if (isWin) {
|
|
172
179
|
// A `.cmd` shim can't be spawned directly under Node (`spawn npx EINVAL`) — run via cmd.exe.
|
|
@@ -187,14 +194,14 @@ export class LspClient {
|
|
|
187
194
|
setTimeout(() => {
|
|
188
195
|
if (!this.initialized && this.process) {
|
|
189
196
|
const msg = "LSP server start timeout";
|
|
190
|
-
this.logger?.error(msg, { command: config.command, projectRoot, timeout: config.timeout ??
|
|
197
|
+
this.logger?.error(msg, { command: config.command, projectRoot, timeout: config.timeout ?? DEFAULT_TIMEOUT });
|
|
191
198
|
reject(new Error(msg));
|
|
192
199
|
}
|
|
193
|
-
}, config.timeout ??
|
|
200
|
+
}, config.timeout ?? DEFAULT_TIMEOUT);
|
|
194
201
|
});
|
|
195
202
|
}
|
|
196
203
|
handleData(chunk) {
|
|
197
|
-
this.buffer
|
|
204
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
198
205
|
this.parseMessages();
|
|
199
206
|
}
|
|
200
207
|
parseMessages() {
|
|
@@ -203,33 +210,36 @@ export class LspClient {
|
|
|
203
210
|
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
204
211
|
if (headerEnd === -1)
|
|
205
212
|
return;
|
|
206
|
-
|
|
213
|
+
// Headers are ASCII — decode just the header slice.
|
|
214
|
+
const header = this.buffer.subarray(0, headerEnd).toString("utf8");
|
|
207
215
|
const match = header.match(/Content-Length: (\d+)/i);
|
|
208
216
|
if (!match) {
|
|
209
|
-
this.buffer = this.buffer.
|
|
210
|
-
this.contentLength = -1;
|
|
217
|
+
this.buffer = this.buffer.subarray(headerEnd + 4);
|
|
211
218
|
continue;
|
|
212
219
|
}
|
|
213
220
|
this.contentLength = parseInt(match[1], 10);
|
|
214
221
|
// Guard against malicious/buggy servers sending absurd Content-Length
|
|
215
222
|
if (this.contentLength > 10 * 1024 * 1024) {
|
|
216
|
-
this.buffer = this.buffer.
|
|
223
|
+
this.buffer = this.buffer.subarray(headerEnd + 4);
|
|
217
224
|
this.contentLength = -1;
|
|
218
225
|
continue;
|
|
219
226
|
}
|
|
220
|
-
this.buffer = this.buffer.
|
|
227
|
+
this.buffer = this.buffer.subarray(headerEnd + 4);
|
|
221
228
|
}
|
|
229
|
+
// Content-Length counts BYTES, not JS characters. Slicing a decoded
|
|
230
|
+
// string by character count corrupts frames containing multi-byte
|
|
231
|
+
// UTF-8 (e.g. Cyrillic diagnostics) — keep the remainder as raw bytes.
|
|
222
232
|
if (this.buffer.length < this.contentLength)
|
|
223
233
|
return;
|
|
224
|
-
const body = this.buffer.
|
|
225
|
-
this.buffer = this.buffer.
|
|
234
|
+
const body = this.buffer.subarray(0, this.contentLength).toString("utf8");
|
|
235
|
+
this.buffer = this.buffer.subarray(this.contentLength);
|
|
226
236
|
this.contentLength = -1;
|
|
227
237
|
try {
|
|
228
238
|
const msg = JSON.parse(body);
|
|
229
239
|
this.handleMessage(msg);
|
|
230
240
|
}
|
|
231
241
|
catch (e) {
|
|
232
|
-
this.logger?.debug(`LSP JSON parse error: ${
|
|
242
|
+
this.logger?.debug(`LSP JSON parse error: ${errMsg(e)}`);
|
|
233
243
|
}
|
|
234
244
|
}
|
|
235
245
|
}
|
|
@@ -290,7 +300,7 @@ export class LspClient {
|
|
|
290
300
|
this.process.stdin.write(header + message);
|
|
291
301
|
}
|
|
292
302
|
catch (e) {
|
|
293
|
-
this.logger?.debug(`LSP write error: ${
|
|
303
|
+
this.logger?.debug(`LSP write error: ${errMsg(e)}`);
|
|
294
304
|
}
|
|
295
305
|
}
|
|
296
306
|
async shutdown() {
|
|
@@ -304,14 +314,14 @@ export class LspClient {
|
|
|
304
314
|
await this.sendRequest("shutdown", null, 3000);
|
|
305
315
|
}
|
|
306
316
|
catch (e) {
|
|
307
|
-
this.logger?.debug(`LSP shutdown request failed: ${
|
|
317
|
+
this.logger?.debug(`LSP shutdown request failed: ${errMsg(e)}`);
|
|
308
318
|
}
|
|
309
319
|
this.sendNotification("exit", null);
|
|
310
320
|
await new Promise((r) => setTimeout(r, 200));
|
|
311
321
|
}
|
|
312
322
|
}
|
|
313
323
|
catch (e) {
|
|
314
|
-
this.logger?.debug(`LSP shutdown error: ${
|
|
324
|
+
this.logger?.debug(`LSP shutdown error: ${errMsg(e)}`);
|
|
315
325
|
}
|
|
316
326
|
finally {
|
|
317
327
|
if (this.process) {
|
|
@@ -322,12 +332,12 @@ export class LspClient {
|
|
|
322
332
|
killTree(this.process);
|
|
323
333
|
}
|
|
324
334
|
catch (e) {
|
|
325
|
-
this.logger?.debug(`LSP killTree error: ${
|
|
335
|
+
this.logger?.debug(`LSP killTree error: ${errMsg(e)}`);
|
|
326
336
|
}
|
|
327
337
|
this.process = null;
|
|
328
338
|
}
|
|
329
339
|
this.initialized = false;
|
|
330
|
-
this.buffer =
|
|
340
|
+
this.buffer = Buffer.alloc(0);
|
|
331
341
|
this.contentLength = -1;
|
|
332
342
|
if (this.diagnosticsTimer) {
|
|
333
343
|
clearTimeout(this.diagnosticsTimer);
|
|
@@ -336,7 +346,7 @@ export class LspClient {
|
|
|
336
346
|
}
|
|
337
347
|
}
|
|
338
348
|
pathToUri(filePath) {
|
|
339
|
-
const normalized = filePath
|
|
349
|
+
const normalized = toForwardSlash(filePath);
|
|
340
350
|
return /^[a-zA-Z]:/.test(normalized) ? `file:///${normalized}` : `file://${normalized}`;
|
|
341
351
|
}
|
|
342
352
|
languageFromPath(filePath) {
|
|
@@ -364,23 +374,30 @@ export class LspClient {
|
|
|
364
374
|
return map[ext ?? ""] ?? "plaintext";
|
|
365
375
|
}
|
|
366
376
|
/**
|
|
367
|
-
* Extract the actual binary name from npx args
|
|
368
|
-
*
|
|
369
|
-
*
|
|
377
|
+
* Extract the actual binary name from npx args — only for the strict layout
|
|
378
|
+
* `[--yes] [--package X]... <binary> --stdio`. Servers with trailing server
|
|
379
|
+
* arguments (e.g. angular's `ngserver --stdio --tsProbeLocations …`) return
|
|
380
|
+
* null and always run through npx, so their flags are never mistaken for the
|
|
381
|
+
* binary or stripped.
|
|
370
382
|
*/
|
|
371
383
|
extractBinaryFromNpxArgs(args) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
384
|
+
const stdioIdx = args.lastIndexOf("--stdio");
|
|
385
|
+
if (stdioIdx !== args.length - 1 || stdioIdx < 1)
|
|
386
|
+
return null;
|
|
387
|
+
const binary = args[stdioIdx - 1];
|
|
388
|
+
if (!binary || binary.startsWith("-") || this.isPackageVersion(binary))
|
|
389
|
+
return null;
|
|
390
|
+
for (let i = 0; i < stdioIdx - 1; i++) {
|
|
391
|
+
const a = args[i];
|
|
392
|
+
if (a === "--yes")
|
|
376
393
|
continue;
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
if (this.isPackageVersion(arg))
|
|
394
|
+
if (a === "--package") {
|
|
395
|
+
i++;
|
|
380
396
|
continue;
|
|
381
|
-
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
382
399
|
}
|
|
383
|
-
return
|
|
400
|
+
return binary;
|
|
384
401
|
}
|
|
385
402
|
/** Check if a string looks like a package version specifier (e.g. "typescript@5"). */
|
|
386
403
|
isPackageVersion(s) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
1
3
|
/**
|
|
2
4
|
* LSP server defaults.
|
|
3
5
|
*
|
|
@@ -101,6 +103,25 @@ export const DEFAULT_LSP_CONFIG = {
|
|
|
101
103
|
autoInstall: true,
|
|
102
104
|
workspaceMarkers: ["package.json"],
|
|
103
105
|
},
|
|
106
|
+
angular: {
|
|
107
|
+
command: "npx",
|
|
108
|
+
args: [
|
|
109
|
+
"--yes",
|
|
110
|
+
"--package",
|
|
111
|
+
"@angular/language-server",
|
|
112
|
+
"--package",
|
|
113
|
+
"typescript@5",
|
|
114
|
+
"ngserver",
|
|
115
|
+
"--stdio",
|
|
116
|
+
"--tsProbeLocations",
|
|
117
|
+
"./node_modules",
|
|
118
|
+
"--ngProbeLocations",
|
|
119
|
+
"./node_modules",
|
|
120
|
+
],
|
|
121
|
+
timeout: 60000,
|
|
122
|
+
autoInstall: true,
|
|
123
|
+
workspaceMarkers: ["angular.json"],
|
|
124
|
+
},
|
|
104
125
|
},
|
|
105
126
|
};
|
|
106
127
|
const LANGUAGE_MAP = {
|
|
@@ -127,9 +148,41 @@ export function getLanguageForFile(filePath) {
|
|
|
127
148
|
return null;
|
|
128
149
|
return LANGUAGE_MAP[ext] ?? null;
|
|
129
150
|
}
|
|
130
|
-
|
|
151
|
+
const ANGULAR_MARKER = "angular.json";
|
|
152
|
+
const ANGULAR_LOOKUP_DEPTH = 10;
|
|
153
|
+
/**
|
|
154
|
+
* True when the file lives inside an Angular workspace (an `angular.json`
|
|
155
|
+
* marker exists in one of its ancestor directories, bounded lookup).
|
|
156
|
+
*/
|
|
157
|
+
function isAngularProject(filePath) {
|
|
158
|
+
let dir = dirname(filePath);
|
|
159
|
+
for (let i = 0; i < ANGULAR_LOOKUP_DEPTH; i++) {
|
|
160
|
+
if (existsSync(join(dir, ANGULAR_MARKER)))
|
|
161
|
+
return true;
|
|
162
|
+
const parent = dirname(dir);
|
|
163
|
+
if (parent === dir)
|
|
164
|
+
return false;
|
|
165
|
+
dir = parent;
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Config key of the LSP server for a file. `.html` routes to the `angular`
|
|
171
|
+
* server inside Angular workspaces (templates with bindings need the Angular
|
|
172
|
+
* language service) and to the generic `html` server everywhere else.
|
|
173
|
+
*/
|
|
174
|
+
export function getServerKeyForFile(filePath, config) {
|
|
131
175
|
const lang = getLanguageForFile(filePath);
|
|
132
176
|
if (!lang)
|
|
133
177
|
return null;
|
|
134
|
-
|
|
178
|
+
if (lang === "html" && config.servers.angular && isAngularProject(filePath)) {
|
|
179
|
+
return "angular";
|
|
180
|
+
}
|
|
181
|
+
return lang;
|
|
182
|
+
}
|
|
183
|
+
export function getServerForFile(filePath, config) {
|
|
184
|
+
const key = getServerKeyForFile(filePath, config);
|
|
185
|
+
if (!key)
|
|
186
|
+
return null;
|
|
187
|
+
return config.servers[key] ?? null;
|
|
135
188
|
}
|