@octocodeai/octocode-engine 16.6.2 → 16.7.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/README.md +150 -109
- package/dist/lsp/client.d.ts +17 -9
- package/dist/lsp/client.js +25 -25
- package/dist/lsp/config.js +1 -1
- package/dist/lsp/lspClientPool.d.ts +2 -0
- package/dist/lsp/lspClientPool.js +9 -4
- package/dist/lsp/manager.js +25 -3
- package/dist/lsp/native.d.ts +2 -1
- package/dist/lsp/resolver.js +10 -4
- package/dist/lsp/serverManifest.d.ts +5 -4
- package/dist/lsp/serverManifest.js +44 -6
- package/dist/lsp/serverManifestData.js +85 -22
- package/dist/lsp/serverProvisioner.js +13 -4
- package/dist/lsp/types.d.ts +11 -0
- package/dist/security/commandValidator.js +64 -0
- package/dist/security/contentSanitizer.js +4 -3
- package/dist/security/discoveryFilter.js +3 -0
- package/dist/security/ignoredPathFilter.js +5 -2
- package/dist/security/pathValidator.js +17 -19
- package/dist/security/registry.js +27 -9
- package/dist/security/securityConstants.d.ts +1 -1
- package/dist/security/securityConstants.js +22 -1
- package/dist/security/withSecurityValidation.js +15 -3
- package/docs/LSP_SERVER_LIFECYCLE.md +258 -0
- package/index.d.ts +8 -2
- package/package.json +12 -9
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
import { normalizeCommandName } from './commandUtils.js';
|
|
2
|
-
const REDOS_TIMEOUT_MS =
|
|
3
|
-
|
|
2
|
+
const REDOS_TIMEOUT_MS = 200;
|
|
3
|
+
// Use multiple input lengths to catch ReDoS patterns that only blow up at larger sizes.
|
|
4
|
+
const REDOS_TEST_INPUTS = [
|
|
5
|
+
'a'.repeat(50),
|
|
6
|
+
'a'.repeat(500),
|
|
7
|
+
'a'.repeat(2000),
|
|
8
|
+
];
|
|
4
9
|
function isReDoSSafe(regex) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
// Timing heuristic — catches obvious exponential backtracking but cannot provide
|
|
11
|
+
// structural guarantees. Tests multiple input sizes to catch patterns that only
|
|
12
|
+
// blow up at larger scales. A proper linear-time checker (e.g., re2) would be
|
|
13
|
+
// the gold standard but adds a native dependency.
|
|
14
|
+
for (const input of REDOS_TEST_INPUTS) {
|
|
15
|
+
regex.lastIndex = 0;
|
|
16
|
+
const start = performance.now();
|
|
17
|
+
try {
|
|
18
|
+
regex.test(input);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
regex.lastIndex = 0;
|
|
25
|
+
}
|
|
26
|
+
if (performance.now() - start >= REDOS_TIMEOUT_MS) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
11
29
|
}
|
|
12
|
-
return
|
|
30
|
+
return true;
|
|
13
31
|
}
|
|
14
32
|
export class SecurityRegistry {
|
|
15
33
|
_extraSecretPatterns = [];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const ALLOWED_COMMANDS: readonly ["rg", "ls", "find", "grep", "git"];
|
|
1
|
+
export declare const ALLOWED_COMMANDS: readonly ["rg", "ls", "find", "grep", "git", "file", "zcat", "gunzip", "bzcat", "xzcat", "zstdcat", "zstd", "lz4cat", "brotli", "lzfse", "tar", "unzip", "bsdtar", "7z", "7zz"];
|
|
2
2
|
export declare const DANGEROUS_PATTERNS: readonly [RegExp, RegExp, RegExp];
|
|
3
3
|
export declare const PATTERN_DANGEROUS_PATTERNS: readonly [RegExp, RegExp, RegExp, RegExp];
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
export const ALLOWED_COMMANDS = [
|
|
1
|
+
export const ALLOWED_COMMANDS = [
|
|
2
|
+
'rg',
|
|
3
|
+
'ls',
|
|
4
|
+
'find',
|
|
5
|
+
'grep',
|
|
6
|
+
'git',
|
|
7
|
+
'file',
|
|
8
|
+
'zcat',
|
|
9
|
+
'gunzip',
|
|
10
|
+
'bzcat',
|
|
11
|
+
'xzcat',
|
|
12
|
+
'zstdcat',
|
|
13
|
+
'zstd',
|
|
14
|
+
'lz4cat',
|
|
15
|
+
'brotli',
|
|
16
|
+
'lzfse',
|
|
17
|
+
'tar',
|
|
18
|
+
'unzip',
|
|
19
|
+
'bsdtar',
|
|
20
|
+
'7z',
|
|
21
|
+
'7zz',
|
|
22
|
+
];
|
|
2
23
|
export const DANGEROUS_PATTERNS = [
|
|
3
24
|
/[;&|`$(){}[\]<>]/, // Shell metacharacters
|
|
4
25
|
/\${/,
|
|
@@ -13,13 +13,24 @@ export function configureSecurity(deps) {
|
|
|
13
13
|
function createErrorResult(text) {
|
|
14
14
|
return { content: [{ type: 'text', text }], isError: true };
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
// Combine an external caller-provided AbortSignal with an internal one so that
|
|
17
|
+
// aborting either cancels the tool. Returns the single source unchanged when
|
|
18
|
+
// only one (or none) is present, preserving existing behavior.
|
|
19
|
+
function mergeAbortSignals(external, internal) {
|
|
20
|
+
if (!external)
|
|
21
|
+
return internal;
|
|
22
|
+
if (!internal)
|
|
23
|
+
return external;
|
|
24
|
+
return AbortSignal.any([external, internal]);
|
|
25
|
+
}
|
|
26
|
+
function withToolTimeout(toolName, promise, signal, timeoutMs, onTimeout) {
|
|
17
27
|
const timeout = getTimeoutMs(timeoutMs);
|
|
18
28
|
if (signal?.aborted) {
|
|
19
29
|
return Promise.resolve(createErrorResult(`Tool '${toolName}' was cancelled before execution.`));
|
|
20
30
|
}
|
|
21
31
|
return new Promise(resolve => {
|
|
22
32
|
const timer = setTimeout(() => {
|
|
33
|
+
onTimeout?.();
|
|
23
34
|
resolve(createErrorResult(`Tool '${toolName}' timed out after ${timeout / 1000}s. Try reducing query complexity or scope.`));
|
|
24
35
|
}, timeout);
|
|
25
36
|
const onAbort = () => {
|
|
@@ -50,7 +61,7 @@ function withToolTimeout(toolName, promise, signal, timeoutMs) {
|
|
|
50
61
|
});
|
|
51
62
|
}
|
|
52
63
|
async function runSecure(opts) {
|
|
53
|
-
const { toolName, handler, args, authInfo, sessionId, signal, timeoutMs } = opts;
|
|
64
|
+
const { toolName, handler, args, authInfo, sessionId, signal, timeoutMs, onTimeout, abortSignal, } = opts;
|
|
54
65
|
try {
|
|
55
66
|
const sanitizer = getSanitizer();
|
|
56
67
|
const validation = sanitizer.validateInputParameters(args);
|
|
@@ -58,7 +69,8 @@ async function runSecure(opts) {
|
|
|
58
69
|
return createErrorResult(`Security validation failed: ${validation.warnings.join('; ')}`);
|
|
59
70
|
}
|
|
60
71
|
const sanitizedParams = validation.sanitizedParams;
|
|
61
|
-
const
|
|
72
|
+
const mergedSignal = mergeAbortSignals(signal, abortSignal);
|
|
73
|
+
const rawResult = await withToolTimeout(toolName, handler(sanitizedParams, authInfo, sessionId), mergedSignal, timeoutMs, onTimeout);
|
|
62
74
|
return rawResult;
|
|
63
75
|
}
|
|
64
76
|
catch (error) {
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# LSP in Octocode — Lifecycle, Provisioning, and the No-Fallback Contract
|
|
2
|
+
|
|
3
|
+
Companion: `docs/context/LSP_GUIDE.md` (protocol primer + platformized resolution ladder).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Two layers, two different jobs
|
|
8
|
+
|
|
9
|
+
| Layer | What it is | Speed | Answers |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| **Tree-sitter / OXC** | parser compiled into the engine | sub-ms/file | *"what does this code look like?"* — outlines, shapes, calls, imports (`structural/`, `signatures/`, `grammar.rs`) |
|
|
12
|
+
| **LSP** | a real language server, spawned over stdio | cold 1–120s, warm <100ms | *"what does this symbol mean?"* — cross-file definition identity, all references, call/type graph (`lsp/client.rs`, `manager.ts`, tools-core `semantic_content/execution.ts`) |
|
|
13
|
+
|
|
14
|
+
These are **not** interchangeable. Tree-sitter cannot resolve a symbol across files, infer a type, or follow an import — that needs a language server.
|
|
15
|
+
|
|
16
|
+
## The no-fallback contract (the rule that matters)
|
|
17
|
+
|
|
18
|
+
When a semantic operation needs a language server and **no server is available**, octocode **throws** — it does *not* fabricate a syntactic or same-file approximation. A faked answer is worse than an honest failure, because the calling agent would trust it.
|
|
19
|
+
|
|
20
|
+
- The thrown error is the standard typed envelope: `status:"error"`, `errorCode:"lspServerUnavailable"`. In bulk it lands under `errors[]`.
|
|
21
|
+
- The message names the language, says no server is available, gives the install hint, and **directs the agent to `localSearchCode` (text/structural search) + `localGetFileContent`** instead.
|
|
22
|
+
- octocode never returns a same-file-only `references` result, or a tree-sitter guess, dressed up as a semantic answer.
|
|
23
|
+
|
|
24
|
+
**Throws when no server:** `definition`, `references`, `hover`, `callers`, `callees`, `callHierarchy`, `typeDefinition`, `implementation`, `workspaceSymbol`, `supertypes`, `subtypes`, `diagnostic`.
|
|
25
|
+
|
|
26
|
+
**Never throws (genuine tree-sitter features, server-free):** `documentSymbols` (native OXC for JS/TS, Markdown heading outline, or LSP when present) and structural/AST search via `localSearchCode`. These are real syntactic capabilities, not LSP stand-ins. `documentSymbols` only throws for a non-JS/TS language with no server *and* no outline.
|
|
27
|
+
|
|
28
|
+
> A server that *is* running but lacks a capability, or returns zero results, still yields an honest *empty* (`unsupportedOperation` / `noReferences` / …) — that is an accurate answer ("none"), not a missing-server failure.
|
|
29
|
+
|
|
30
|
+
## Provisioning — three classes (how a server becomes available)
|
|
31
|
+
|
|
32
|
+
octocode maximizes the chance a real server answers via the resolution ladder (override → PATH → bundled → ecosystem discovery → managed cache; see `LSP_GUIDE.md` §13). Use `npx octocode lsp-server list` to see all servers and their current status, `npx octocode lsp-server status <file>` to check resolution for a specific file, and `npx octocode lsp-server install <name>` to trigger an auto-download. Servers fall into:
|
|
33
|
+
|
|
34
|
+
- **Bundled (npm dep, offline)** — pure-JS servers launched with the current Node; zero install. TS/JS, Python (pyright), Shell (bash-language-server), PHP (intelephense), YAML, JSON/HTML/CSS.
|
|
35
|
+
- **Auto-download (managed cache)** — portable single-binary servers fetched from a pinned release into `~/.octocode/lsp/<server>/<tag>/` (prompt-by-default, SHA-verified). rust-analyzer (all platforms), clangd (no linux-arm64 asset). Set `OCTOCODE_LSP_AUTO_INSTALL=auto` to skip the prompt or `=off` to disable downloads entirely.
|
|
36
|
+
- **Detect-and-instruct (host toolchain)** — need a runtime octocode won't auto-install: gopls (Go), jdtls (JDK 21+), sourcekit-lsp (Xcode/CLI tools on macOS), csharp-ls (.NET SDK). The status/hint tells you how to install; semantic ops throw until you do.
|
|
37
|
+
|
|
38
|
+
## Supported language servers
|
|
39
|
+
|
|
40
|
+
Scope is the **main languages**. Niche/long-tail servers were intentionally removed from
|
|
41
|
+
LSP routing (their tree-sitter grammars remain for structural/AST search). A file type not
|
|
42
|
+
listed below has no server config: semantic ops return `lspServerUnavailable` and the agent
|
|
43
|
+
falls back to text search.
|
|
44
|
+
|
|
45
|
+
| Language | Extensions | Server | Provisioning |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| TypeScript / JS (+ TSX/JSX) | `.ts .mts .cts .tsx .js .mjs .cjs .jsx` | typescript-language-server (`tsgo`/override aware) | **bundled** |
|
|
48
|
+
| Python | `.py .pyi` | pyright (`pylsp` via override) | **bundled** |
|
|
49
|
+
| Shell | `.sh` | bash-language-server | **bundled** |
|
|
50
|
+
| PHP | `.php` | intelephense | **bundled** |
|
|
51
|
+
| YAML | `.yaml .yml` | yaml-language-server | **bundled** |
|
|
52
|
+
| JSON | `.json .jsonc` | vscode-json-language-server | **bundled** |
|
|
53
|
+
| HTML | `.html .htm` | vscode-html-language-server | **bundled** |
|
|
54
|
+
| CSS / SCSS / LESS | `.css .scss .less` | vscode-css-language-server | **bundled** |
|
|
55
|
+
| Rust | `.rs` | rust-analyzer | **auto-download** |
|
|
56
|
+
| C / C++ | `.c .h .cpp .cc .cxx .hpp` | clangd | **auto-download** (no linux-arm64 asset) |
|
|
57
|
+
| Go | `.go` | gopls | **detect-and-instruct** (needs Go toolchain) |
|
|
58
|
+
| Java | `.java` | jdtls | **detect-and-instruct** (needs JDK 21+) |
|
|
59
|
+
| Swift | `.swift` | sourcekit-lsp | **detect-and-instruct** (needs Xcode or `xcode-select --install`) |
|
|
60
|
+
| C# | `.cs` | csharp-ls | **detect-and-instruct** (needs .NET SDK + `dotnet tool install -g csharp-ls`) |
|
|
61
|
+
| SQL | `.sql` | sqls | **PATH / override only** |
|
|
62
|
+
|
|
63
|
+
Any built-in server can be overridden with `OCTOCODE_<LANG>_SERVER_PATH` or `.octocode/lsp-servers.json`.
|
|
64
|
+
PATH/override-only servers resolve only if already on `PATH` / in an ecosystem dir; otherwise
|
|
65
|
+
semantic ops throw with an install hint.
|
|
66
|
+
|
|
67
|
+
**Removed from LSP routing** (use text/structural search instead): TOML, Ruby, Kotlin,
|
|
68
|
+
Elixir, Terraform, Lua, Proto, OCaml, Zig, Julia, Erlang, R, GDScript. Their tree-sitter
|
|
69
|
+
grammars stay available for `localSearchCode` structural/AST queries.
|
|
70
|
+
|
|
71
|
+
### Custom / bring-your-own LSP (any language)
|
|
72
|
+
|
|
73
|
+
A language with **no built-in spec** (e.g. Scala, Kotlin, Ruby) gets full semantic support by
|
|
74
|
+
registering a server in a JSON config — no rebuild, no code change. This is also how you swap a
|
|
75
|
+
built-in server for a different one. Resolution reads, in order (`config.rs::user_config_paths`):
|
|
76
|
+
|
|
77
|
+
1. `$OCTOCODE_LSP_CONFIG` (explicit file path)
|
|
78
|
+
2. `<workspace>/.octocode/lsp-servers.json` (per-project, checked in or local)
|
|
79
|
+
3. `~/.octocode/lsp-servers.json` (per-user, all projects)
|
|
80
|
+
|
|
81
|
+
The file maps a **file extension** to a launch spec. A custom entry takes precedence over the
|
|
82
|
+
built-in spec for that extension:
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
// .octocode/lsp-servers.json — register Scala (metals)
|
|
86
|
+
{
|
|
87
|
+
"languageServers": {
|
|
88
|
+
".scala": { "command": "metals", "args": ["stdio"], "languageId": "scala" },
|
|
89
|
+
".sc": { "command": "metals", "args": ["stdio"], "languageId": "scala" }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Field | Required | Meaning |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `command` | yes | Executable name (resolved on `PATH`) or absolute path. Shell wrappers are rejected. |
|
|
97
|
+
| `languageId` | yes | LSP `languageId` sent on `textDocument/didOpen` (e.g. `scala`, `ruby`). |
|
|
98
|
+
| `args` | no | Launch args (default `[]`). |
|
|
99
|
+
| `initializationOptions` | no | Passed verbatim in the LSP `initialize` request. |
|
|
100
|
+
|
|
101
|
+
With the config present, semantic ops (`definition`, `references`, `hover`, call hierarchy, …)
|
|
102
|
+
work for that language exactly like a built-in one. **Without it, the extension stays unsupported:
|
|
103
|
+
the engine resolves no server and the no-fallback contract applies** — semantic ops throw
|
|
104
|
+
`lspServerUnavailable` and the agent falls back to `localSearchCode` + `localGetFileContent`.
|
|
105
|
+
Both halves of this contract are asserted by the benchmark (`benchmark/lsp/check-lsp.mjs`,
|
|
106
|
+
"Custom LSP — bring-your-own server (Scala / metals)"), and verified **live against a real
|
|
107
|
+
server** by `benchmark/lsp/check-custom-lsp.mjs` (`yarn lsp:custom`) — which registers
|
|
108
|
+
`bash-language-server` (a language with no built-in spec) and runs real `documentSymbols` /
|
|
109
|
+
`references` / `hover` through it.
|
|
110
|
+
|
|
111
|
+
### Markup & docs: what's LSP vs minify
|
|
112
|
+
|
|
113
|
+
- **HTML / CSS / SCSS / LESS / JSON / YAML are LSP** — served by the bundled
|
|
114
|
+
`vscode-*-language-server` / `yaml-language-server` (markup/data, offline-ready). They're
|
|
115
|
+
not "code" languages but they do have real language servers.
|
|
116
|
+
- **Markdown / MDX are NOT LSP.** They are handled by the **minifier** using heading-section
|
|
117
|
+
heuristics (ATX `#`/`##` and setext headings → `minify/strategies/markdown.rs`; `md`,
|
|
118
|
+
`markdown`, `mdx` all map to the markdown strategy in `minify/config.rs`). There is no
|
|
119
|
+
markdown language server in octocode — `documentSymbols` on a `.md` file uses the native
|
|
120
|
+
heading-outline path, and structure/compression comes from the minifier, not a server.
|
|
121
|
+
|
|
122
|
+
## Full format support matrix
|
|
123
|
+
|
|
124
|
+
Per-extension capabilities across all four axes — minify strategy, structural AST,
|
|
125
|
+
signature outline, and LSP server — machine-generated from the shipped napi binary.
|
|
126
|
+
The LSP column here is the per-extension view of the [Supported language servers](#supported-language-servers)
|
|
127
|
+
table above.
|
|
128
|
+
|
|
129
|
+
<!-- BEGIN GENERATED: support-matrix (yarn matrix:check --write) — do not edit between these markers -->
|
|
130
|
+
|
|
131
|
+
_Generated by `yarn matrix:check --write` (benchmark/check-matrix.mjs) — every cell probed live against the shipped napi binary. Do not edit between the markers; run `yarn matrix:check` to re-verify._
|
|
132
|
+
|
|
133
|
+
> **Note:** The matrix queries the native layer (`config.rs`) only. Two servers injected at the TS layer (`config.ts`) are not reflected: **bash-language-server** for `.sh` (languageId `shellscript`) and **intelephense** for `.php` (the native spec emits `intelephense` as command — both bundled and active). Their LSP cells show `—` in the matrix below but the servers are bundled and resolve correctly (`npx octocode lsp-server status <file>` will show `resolved: bundled`).
|
|
134
|
+
|
|
135
|
+
**151 extensions** known to the engine — 61 with structural AST, 47 with a signature outline, 32 with an LSP server, 90 minify-only.
|
|
136
|
+
|
|
137
|
+
### Rich formats — AST + signature + LSP
|
|
138
|
+
|
|
139
|
+
Extensions with a wired tree-sitter grammar (and, where configured, a language server). The minify column is the configured strategy.
|
|
140
|
+
|
|
141
|
+
| Extension | Minify | Structural AST | Signature outline | LSP (server → language-id) |
|
|
142
|
+
|-----------|--------|:--------------:|-------------------|----------------------------|
|
|
143
|
+
| `.bash` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
144
|
+
| `.c` | `conservative` | ✅ | ✅ tree-sitter | `clangd` → `c` |
|
|
145
|
+
| `.cc` | `conservative` | ✅ | ✅ tree-sitter | `clangd` → `cpp` |
|
|
146
|
+
| `.cjs` | `terser` | ✅ | ✅ tree-sitter | `typescript-language-server` → `javascript` |
|
|
147
|
+
| `.cpp` | `conservative` | ✅ | ✅ tree-sitter | `clangd` → `cpp` |
|
|
148
|
+
| `.cs` | `conservative` | ✅ | ✅ tree-sitter | `csharp-ls` → `csharp` |
|
|
149
|
+
| `.css` | `aggressive` | ✅ | — | `vscode-css-language-server` → `css` |
|
|
150
|
+
| `.cts` | `conservative` | ✅ | ✅ tree-sitter | `typescript-language-server` → `typescript` |
|
|
151
|
+
| `.cxx` | `conservative` | ✅ | ✅ tree-sitter | `clangd` → `cpp` |
|
|
152
|
+
| `.erl` | `aggressive` | ✅ | ✅ tree-sitter | — |
|
|
153
|
+
| `.ex` | `aggressive` | ✅ | ✅ tree-sitter | — |
|
|
154
|
+
| `.exs` | `aggressive` | ✅ | ✅ tree-sitter | — |
|
|
155
|
+
| `.gemspec` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
156
|
+
| `.go` | `conservative` | ✅ | ✅ tree-sitter | `gopls` → `go` |
|
|
157
|
+
| `.h` | `conservative` | ✅ | ✅ tree-sitter | `clangd` → `c` |
|
|
158
|
+
| `.hcl` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
159
|
+
| `.hh` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
160
|
+
| `.hpp` | `conservative` | ✅ | ✅ tree-sitter | `clangd` → `cpp` |
|
|
161
|
+
| `.hrl` | `aggressive` | ✅ | ✅ tree-sitter | — |
|
|
162
|
+
| `.htm` | `aggressive` | ✅ | — | `vscode-html-language-server` → `html` |
|
|
163
|
+
| `.html` | `aggressive` | ✅ | — | `vscode-html-language-server` → `html` |
|
|
164
|
+
| `.hxx` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
165
|
+
| `.java` | `conservative` | ✅ | ✅ tree-sitter | `jdtls` → `java` |
|
|
166
|
+
| `.jl` | `conservative` | ✅ | — | — |
|
|
167
|
+
| `.js` | `terser` | ✅ | ✅ tree-sitter | `typescript-language-server` → `javascript` |
|
|
168
|
+
| `.json` | `json` | ✅ | — | `vscode-json-language-server` → `json` |
|
|
169
|
+
| `.jsonc` | `json` | ✅ | — | `vscode-json-language-server` → `json` |
|
|
170
|
+
| `.jsx` | `terser` | ✅ | ✅ tree-sitter | `typescript-language-server` → `javascriptreact` |
|
|
171
|
+
| `.kt` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
172
|
+
| `.kts` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
173
|
+
| `.less` | `aggressive` | ✅ | — | `vscode-css-language-server` → `less` |
|
|
174
|
+
| `.lua` | `aggressive` | ✅ | ✅ tree-sitter | — |
|
|
175
|
+
| `.mjs` | `terser` | ✅ | ✅ tree-sitter | `typescript-language-server` → `javascript` |
|
|
176
|
+
| `.ml` | `conservative` | ✅ | — | — |
|
|
177
|
+
| `.mli` | `conservative` | ✅ | — | — |
|
|
178
|
+
| `.mts` | `conservative` | ✅ | ✅ tree-sitter | `typescript-language-server` → `typescript` |
|
|
179
|
+
| `.php` | `conservative` | ✅ | ✅ tree-sitter | `intelephense` → `php` |
|
|
180
|
+
| `.proto` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
181
|
+
| `.py` | `conservative` | ✅ | ✅ tree-sitter | `pylsp` → `python` |
|
|
182
|
+
| `.pyi` | `conservative` | ✅ | ✅ tree-sitter | `pylsp` → `python` |
|
|
183
|
+
| `.r` | `aggressive` | ✅ | ✅ tree-sitter | — |
|
|
184
|
+
| `.rake` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
185
|
+
| `.rb` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
186
|
+
| `.rs` | `conservative` | ✅ | ✅ tree-sitter | `rust-analyzer` → `rust` |
|
|
187
|
+
| `.ru` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
188
|
+
| `.sbt` | — | ✅ | ✅ tree-sitter | — |
|
|
189
|
+
| `.sc` | — | ✅ | ✅ tree-sitter | — |
|
|
190
|
+
| `.scala` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
191
|
+
| `.scss` | `aggressive` | ✅ | — | `vscode-css-language-server` → `scss` |
|
|
192
|
+
| `.sh` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
193
|
+
| `.sql` | `conservative` | ✅ | — | `sqls` → `sql` |
|
|
194
|
+
| `.swift` | `conservative` | ✅ | ✅ tree-sitter | `sourcekit-lsp` → `swift` |
|
|
195
|
+
| `.tf` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
196
|
+
| `.tfvars` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
197
|
+
| `.toml` | `conservative` | ✅ | — | — |
|
|
198
|
+
| `.ts` | `conservative` | ✅ | ✅ tree-sitter | `typescript-language-server` → `typescript` |
|
|
199
|
+
| `.tsx` | `conservative` | ✅ | ✅ tree-sitter | `typescript-language-server` → `typescriptreact` |
|
|
200
|
+
| `.yaml` | `conservative` | ✅ | — | `yaml-language-server` → `yaml` |
|
|
201
|
+
| `.yml` | `conservative` | ✅ | — | `yaml-language-server` → `yaml` |
|
|
202
|
+
| `.zig` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
203
|
+
| `.zsh` | `conservative` | ✅ | ✅ tree-sitter | — |
|
|
204
|
+
|
|
205
|
+
Notes:
|
|
206
|
+
- **Signature outline is tree-sitter only** — markup/style/config grammars (HTML/CSS/SCSS/LESS/Scala/JSON/YAML/TOML) parse for structural `rule` queries but have no function body, so no skeleton. There is **no** regex/heuristic fallback.
|
|
207
|
+
- **`.jsx`** resolves the LSP server as `javascriptreact` (to enable JSX) even though its tree-sitter grammar registry id is `javascript` (shared JS grammar). `.tsx` has its own grammar, so both ids are `typescriptreact`.
|
|
208
|
+
- **`.hh` / `.hxx`** have the C++ grammar + signatures but **no clangd server config** (only `.cpp/.cc/.cxx/.hpp` are mapped).
|
|
209
|
+
- **C/C++**: structural `rule` queries (e.g. `kind: call_expression`) work fully; a bare call-shaped `pattern` can hit tree-sitter's declaration-vs-call ambiguity — prefer a `rule` with `kind`. JS/TS also have a native (oxc) symbol/in-file-reference path that needs **no server installed**.
|
|
210
|
+
|
|
211
|
+
### Minify-only formats
|
|
212
|
+
|
|
213
|
+
Native comment/whitespace stripping; no AST/LSP. (90 extensions, grouped by strategy.)
|
|
214
|
+
|
|
215
|
+
**`aggressive`** (18): `clj` `cljs` `ejs` `erb` `handlebars` `hbs` `jinja` `jinja2` `mustache` `pl` `pm` `svelte` `svg` `twig` `vue` `xml` `xsl` `xslt`
|
|
216
|
+
|
|
217
|
+
**`conservative`** (66): `adb` `ads` `asm` `awk` `bzl` `cfg` `cmake` `coffee` `conf` `config` `csv` `dart` `dockerignore` `elm` `env` `f` `f03` `f08` `f90` `f95` `fish` `for` `fs` `fsx` `gitignore` `gql` `gradle` `graphql` `groovy` `haml` `hs` `ini` `jade` `kotlin` `lhs` `lisp` `lsp` `mm` `nasm` `nim` `nix` `pas` `perl` `plsql` `pp` `properties` `ps1` `psd1` `psm1` `pug` `rkt` `rst` `rust` `sass` `scm` `slim` `star` `styl` `tsql` `v` `vb` `vbs` `vhd` `vhdl` `wast` `wat`
|
|
218
|
+
|
|
219
|
+
**`general`** (2): `log` `txt`
|
|
220
|
+
|
|
221
|
+
**`json`** (1): `json5`
|
|
222
|
+
|
|
223
|
+
**`markdown`** (3): `markdown` `md` `mdx`
|
|
224
|
+
|
|
225
|
+
### Verify
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
yarn matrix:check # this matrix, live
|
|
229
|
+
yarn ast:check # structural search + signatures on real samples
|
|
230
|
+
yarn lsp:check # language-id + server resolution + native semantics
|
|
231
|
+
yarn lsp:live # spawn a real server, exercise every LSP operation type
|
|
232
|
+
yarn minify:check # minifier over every configured format
|
|
233
|
+
yarn benchmark # all of the above
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
<!-- END GENERATED: support-matrix -->
|
|
237
|
+
|
|
238
|
+
## Lifecycle — pool, cold start, indexing
|
|
239
|
+
|
|
240
|
+
- **Pool** (`lspClientPool.ts`): one warm `LSPClient` per (server × workspace), 60s idle timeout (`OCTOCODE_LSP_POOL_IDLE_MS`). A long-lived MCP session reuses warm servers across tool calls; one-shot CLI invocations don't share a pool.
|
|
241
|
+
- **Cold start / indexing**: a server reads the project and builds its model before answering correctly. Costs vary — typescript-language-server <1s, gopls 3–15s, rust-analyzer 5–60s (multiple `$/progress` waves), jdtls 30–120s.
|
|
242
|
+
- **Readiness** (`manager.ts` + `json_rpc.rs`): for servers that emit `$/progress` (go, rust, java, csharp, swift) the pool factory calls `waitForReady` with a per-language cap before the first query; servers without `$/progress` (TS/JS, Python, clangd, data formats) skip the wait to avoid a fixed 2s settle penalty.
|
|
243
|
+
- **Spawn gate**: every resolved command passes `validateLSPServerPath` (rejects shell wrappers / nonexistent / non-executable) in `LSPClient.start()` before the process is spawned.
|
|
244
|
+
- **Discovery caching** (`serverDiscovery.ts`): ecosystem-dir lookup results are memoised per `(command, workspaceRoot)` for the process lifetime. Ecosystem dirs are pre-filtered to existing ones once, cutting stat calls from ~15-per-server to ~5. Call `clearDiscoveryCache()` (or restart) after installing a server mid-session.
|
|
245
|
+
|
|
246
|
+
## Open / future (non-blocking)
|
|
247
|
+
|
|
248
|
+
Progress streaming to the caller (`lsp.indexingStatus`), an opt-in `waitForIndexingMs`, tree-sitter symbol extraction for compiled-language `documentSymbols`, and a SCIP precomputed index — all deferred; none change the no-fallback contract above.
|
|
249
|
+
|
|
250
|
+
> **Known issue — cold `references` under-reports.** On a one-shot CLI invocation,
|
|
251
|
+
> `references` (and other project-wide ops) can return incomplete results
|
|
252
|
+
> *labelled* `complete=true`, because `typescript-language-server` emits no
|
|
253
|
+
> `$/progress` and the project isn't indexed yet when queried. This is the one
|
|
254
|
+
> behaviour that can make an agent wrong rather than just slow — analysis and the
|
|
255
|
+
> proposed fix (honest `complete=false` + cross-check hint, with an opt-in
|
|
256
|
+
> `waitForIndexingMs`) are in
|
|
257
|
+
> [`LSP_REFERENCES_INDEXING_RFC.md`](https://github.com/bgauryy/octocode/blob/main/docs/context/LSP_REFERENCES_INDEXING_RFC.md). Meanwhile,
|
|
258
|
+
> prefer `--op callers` or `localSearchCode` to confirm "who uses this".
|
package/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export declare class NativeLspClient {
|
|
|
4
4
|
constructor(config: JsLanguageServerConfig)
|
|
5
5
|
start(): Promise<void>
|
|
6
6
|
stop(): Promise<void>
|
|
7
|
-
|
|
7
|
+
/** Readiness after post-`initialized` indexing: `progressIdle` = a `$/progress` cycle drained to idle; `settledFallback` = no progress seen, only the settle window elapsed; `timeout` = progress still active at the deadline. */
|
|
8
|
+
waitForReady(timeoutMs?: number | undefined | null): Promise<'progressIdle' | 'settledFallback' | 'timeout'>
|
|
8
9
|
hasCapability(capability: string): boolean
|
|
9
10
|
/** Server-selected LSP `positionEncoding` (utf-16 unless the server is non-conformant); null if omitted/not started. */
|
|
10
11
|
positionEncoding(): string | null
|
|
@@ -943,8 +944,13 @@ export interface StructuralQueryExplanation {
|
|
|
943
944
|
|
|
944
945
|
export interface StructuralDetailedMatch extends StructuralMatch {
|
|
945
946
|
id: string
|
|
947
|
+
/** tree-sitter `kind` of the matched node (e.g. `call_expression`). */
|
|
946
948
|
nodeKind?: string
|
|
947
|
-
|
|
949
|
+
/**
|
|
950
|
+
* The octo matcher is a precise AST matcher, so every match is an exact
|
|
951
|
+
* tree-sitter node match — there is no partial/fallback tier.
|
|
952
|
+
*/
|
|
953
|
+
confidence: 'exact-ast'
|
|
948
954
|
}
|
|
949
955
|
|
|
950
956
|
export interface StructuralSearchDetailedResult {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@octocodeai/octocode-engine",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.7.0",
|
|
4
4
|
"description": "Rust native Octocode engine for context compression, structural search, and LSP semantic navigation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.cjs",
|
|
@@ -256,18 +256,19 @@
|
|
|
256
256
|
},
|
|
257
257
|
"files": [
|
|
258
258
|
"dist",
|
|
259
|
+
"docs/**",
|
|
259
260
|
"index.cjs",
|
|
260
261
|
"index.js",
|
|
261
262
|
"index.d.ts",
|
|
262
263
|
"README.md"
|
|
263
264
|
],
|
|
264
265
|
"optionalDependencies": {
|
|
265
|
-
"@octocodeai/octocode-engine-darwin-arm64": "16.
|
|
266
|
-
"@octocodeai/octocode-engine-darwin-x64": "16.
|
|
267
|
-
"@octocodeai/octocode-engine-linux-arm64-gnu": "16.
|
|
268
|
-
"@octocodeai/octocode-engine-linux-x64-gnu": "16.
|
|
269
|
-
"@octocodeai/octocode-engine-linux-x64-musl": "16.
|
|
270
|
-
"@octocodeai/octocode-engine-win32-x64-msvc": "16.
|
|
266
|
+
"@octocodeai/octocode-engine-darwin-arm64": "16.7.0",
|
|
267
|
+
"@octocodeai/octocode-engine-darwin-x64": "16.7.0",
|
|
268
|
+
"@octocodeai/octocode-engine-linux-arm64-gnu": "16.7.0",
|
|
269
|
+
"@octocodeai/octocode-engine-linux-x64-gnu": "16.7.0",
|
|
270
|
+
"@octocodeai/octocode-engine-linux-x64-musl": "16.7.0",
|
|
271
|
+
"@octocodeai/octocode-engine-win32-x64-msvc": "16.7.0"
|
|
271
272
|
},
|
|
272
273
|
"engines": {
|
|
273
274
|
"node": ">=20.0.0"
|
|
@@ -302,8 +303,10 @@
|
|
|
302
303
|
"platforms:check": "node scripts/check-platform-binaries.cjs",
|
|
303
304
|
"version:check": "node scripts/check-version-consistency.cjs",
|
|
304
305
|
"version:sync": "node scripts/sync-versions.cjs",
|
|
306
|
+
"manifest:gen": "node scripts/gen-server-manifest.mjs",
|
|
307
|
+
"verify:manifest": "node scripts/gen-server-manifest.mjs && git diff --exit-code src/lsp/serverManifestData.ts || (echo 'ERROR: src/lsp/serverManifestData.ts is out of sync with serverManifest.json — run yarn manifest:gen and commit the result' && exit 1)",
|
|
305
308
|
"loader:check": "node scripts/check-loader-entries.cjs",
|
|
306
|
-
"prepublish:verify": "yarn version:check && yarn loader:check && yarn pack:check && yarn platforms:check",
|
|
309
|
+
"prepublish:verify": "yarn version:check && yarn loader:check && yarn verify:manifest && yarn pack:check && yarn platforms:check",
|
|
307
310
|
"binary-size:check": "node scripts/check-binary-size.cjs",
|
|
308
311
|
"ast:check": "yarn workspace @octocodeai/octocode-benchmark ast:check",
|
|
309
312
|
"lsp:check": "yarn workspace @octocodeai/octocode-benchmark lsp:check",
|
|
@@ -332,7 +335,7 @@
|
|
|
332
335
|
"audit:rust": "cargo audit -D unmaintained -D unsound -D yanked",
|
|
333
336
|
"audit": "yarn audit:rust",
|
|
334
337
|
"verify:rust": "yarn check:rust && yarn fmt:rust:check && yarn lint:rust && yarn test:rust && yarn deps:rust && yarn udeps:rust && yarn audit:rust",
|
|
335
|
-
"verify": "yarn verify:patterns && yarn typecheck && yarn verify:rust && yarn test && yarn benchmark && yarn pack:check && yarn version:check && yarn loader:check && yarn binary-size:check",
|
|
338
|
+
"verify": "yarn verify:patterns && yarn verify:manifest && yarn typecheck && yarn verify:rust && yarn test && yarn benchmark && yarn pack:check && yarn version:check && yarn loader:check && yarn binary-size:check",
|
|
336
339
|
"readme:sync": "node ../../scripts/sync-package-readmes.mjs .",
|
|
337
340
|
"prepack": "yarn readme:sync",
|
|
338
341
|
"prepublishOnly": "yarn version:sync"
|