gitnexus 1.6.4-rc.80 → 1.6.4-rc.82
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/dist/cli/analyze.js +27 -0
- package/dist/cli/mcp.d.ts +21 -0
- package/dist/cli/mcp.js +42 -13
- package/dist/cli/optional-grammars.d.ts +47 -0
- package/dist/cli/optional-grammars.js +78 -0
- package/dist/cli/setup.js +21 -6
- package/dist/core/embeddings/embedder.js +8 -8
- package/dist/core/embeddings/embedding-pipeline.js +10 -10
- package/dist/core/lbug/extension-loader.js +1 -1
- package/dist/core/lbug/lbug-adapter.js +3 -3
- package/dist/core/lbug/pool-adapter.d.ts +1 -4
- package/dist/core/lbug/pool-adapter.js +19 -5
- package/dist/core/tree-sitter/parser-loader.js +4 -4
- package/dist/mcp/compatible-stdio-transport.js +7 -1
- package/dist/mcp/core/lbug-adapter.d.ts +7 -1
- package/dist/mcp/core/lbug-adapter.js +7 -1
- package/dist/mcp/server.js +18 -7
- package/dist/mcp/stdio-capture.d.ts +40 -0
- package/dist/mcp/stdio-capture.js +53 -0
- package/dist/mcp/stdio-context.d.ts +47 -0
- package/dist/mcp/stdio-context.js +145 -0
- package/package.json +2 -1
- package/scripts/build-tree-sitter-dart.cjs +11 -0
- package/scripts/build-tree-sitter-proto.cjs +11 -0
- package/web/assets/{agent-BR6JgzUS.js → agent-DGm5BiXg.js} +2 -2
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-CzpqonpT.js +1 -0
- package/web/assets/{architectureDiagram-EMZXCZ2Q-DdOrld62.js → architectureDiagram-EMZXCZ2Q-DLWvvvUB.js} +1 -1
- package/web/assets/{blockDiagram-IGV67L2C-DBbcDJE6.js → blockDiagram-IGV67L2C-B47EUMKY.js} +1 -1
- package/web/assets/{c4Diagram-DFAF54RM-BI-8yWq5.js → c4Diagram-DFAF54RM-DvvVQASF.js} +1 -1
- package/web/assets/{chunk-3GS5O3IE-DnMyU1lf.js → chunk-3GS5O3IE-0H2zS7RE.js} +1 -1
- package/web/assets/{chunk-3YCYZ6SJ-BZslrNTQ.js → chunk-3YCYZ6SJ-DokVoiwM.js} +1 -1
- package/web/assets/{chunk-6NTNNK5N-Y67ka-rU.js → chunk-6NTNNK5N-B9jhOxT0.js} +1 -1
- package/web/assets/{chunk-A34GCYZU-ClNpO7ap.js → chunk-A34GCYZU-DfTldqlz.js} +1 -1
- package/web/assets/{chunk-DJ7UZH7F-tYXU3fBz.js → chunk-DJ7UZH7F-npvsBmvn.js} +1 -1
- package/web/assets/{chunk-DKKBVRCY-DfGJMrSz.js → chunk-DKKBVRCY-DbFEYV2a.js} +2 -2
- package/web/assets/{chunk-DU5LTGQ6-qCXmLiFZ.js → chunk-DU5LTGQ6-B1Mj73sD.js} +1 -1
- package/web/assets/{chunk-FXACKDTF-D-WiQLyP.js → chunk-FXACKDTF-CNljDI1M.js} +1 -1
- package/web/assets/{chunk-H3VCZNTA-D3vbWWyQ.js → chunk-H3VCZNTA-j4ZGeRTZ.js} +1 -1
- package/web/assets/{chunk-HN6EAY2L-CQC4-xpr.js → chunk-HN6EAY2L-CEc2xC-J.js} +1 -1
- package/web/assets/{chunk-O5ABG6QK-CqQgDXv5.js → chunk-O5ABG6QK-B_00CmQB.js} +1 -1
- package/web/assets/{chunk-PK6DOVAG-BCVBr6ZD.js → chunk-PK6DOVAG-lOMJKFOu.js} +1 -1
- package/web/assets/{chunk-RNJOYNJ4-Bs1qBdCE.js → chunk-RNJOYNJ4-BKKlhT6X.js} +1 -1
- package/web/assets/{chunk-RWUO3TPN-eG5qgc5e.js → chunk-RWUO3TPN-BhUzGpFd.js} +1 -1
- package/web/assets/{chunk-TBF5ZNIQ-CzrYvTQC.js → chunk-TBF5ZNIQ-BAsGZFMI.js} +1 -1
- package/web/assets/{chunk-TYMNRAUI-BBuddluh.js → chunk-TYMNRAUI-B47lwFjp.js} +1 -1
- package/web/assets/{chunk-W7ZLLLMY-JfkpG9pC.js → chunk-W7ZLLLMY-DGb6u0pB.js} +1 -1
- package/web/assets/{chunk-WSB5WSVC-BSmELgSM.js → chunk-WSB5WSVC-BiaAMFmd.js} +1 -1
- package/web/assets/{chunk-XGPFEOL4-CmPwWVXe.js → chunk-XGPFEOL4-Dqk5iHgi.js} +1 -1
- package/web/assets/classDiagram-PPOCWD7C-W4Gq4oYa.js +1 -0
- package/web/assets/classDiagram-v2-23LJLIIU-CbyGxpD-.js +1 -0
- package/web/assets/{cose-bilkent-PNC4W37J-B2PBKVII.js → cose-bilkent-PNC4W37J-CnOlxR0_.js} +1 -1
- package/web/assets/{dagre-E77IOHMT-DOJQpMhU.js → dagre-E77IOHMT-Cy3z5r06.js} +1 -1
- package/web/assets/{diagram-H7BISOXX-BwuBSflT.js → diagram-H7BISOXX-ChLdgktB.js} +1 -1
- package/web/assets/{diagram-JC5VWROH-RUxO3qvC.js → diagram-JC5VWROH-DdzGpm08.js} +1 -1
- package/web/assets/{diagram-LXUTUG65-DL3slkcr.js → diagram-LXUTUG65-Bq_ye_lS.js} +1 -1
- package/web/assets/{diagram-WEHSV5V5-D-xIWqCV.js → diagram-WEHSV5V5-WrNO_VoZ.js} +1 -1
- package/web/assets/{erDiagram-GCSMX5X6-BsNUcWxA.js → erDiagram-GCSMX5X6-Cx3Aq4SN.js} +1 -1
- package/web/assets/{flowDiagram-OTCZ4VVT-Dez0Lj1Y.js → flowDiagram-OTCZ4VVT-DRe-9T0b.js} +1 -1
- package/web/assets/{ganttDiagram-MUNLMDZQ-ChSXA0kN.js → ganttDiagram-MUNLMDZQ-CvtFMhi8.js} +1 -1
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-DZfbulFG.js +1 -0
- package/web/assets/{gitGraphDiagram-3HKGZ4G3-8iHl9xKL.js → gitGraphDiagram-3HKGZ4G3-BxDd7tFR.js} +1 -1
- package/web/assets/{index-DnjscxVd.js → index-ChQJsgDb.js} +5 -5
- package/web/assets/info-OMHHGYJF-BF2H5H6G-Dehqc8IA.js +1 -0
- package/web/assets/infoDiagram-MN7RKWGX-B0B5J4EY.js +2 -0
- package/web/assets/{ishikawaDiagram-YMYX4NHK-BPFoHRQ-.js → ishikawaDiagram-YMYX4NHK-TUICrJKY.js} +1 -1
- package/web/assets/{journeyDiagram-SO5T7YLQ-DVRJpEUc.js → journeyDiagram-SO5T7YLQ-C4Y1ypn8.js} +1 -1
- package/web/assets/{kanban-definition-LJHFXRCJ-DOUCiWuN.js → kanban-definition-LJHFXRCJ--cIG9jHd.js} +1 -1
- package/web/assets/{mindmap-definition-2EUWGEK5-BNXeeUBG.js → mindmap-definition-2EUWGEK5-BK54zuWp.js} +1 -1
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-BjetiCTk.js +1 -0
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-zkxhGq7v.js +1 -0
- package/web/assets/{pieDiagram-3IATQBI2-6ktwo7Jm.js → pieDiagram-3IATQBI2-CMgPhL6Y.js} +1 -1
- package/web/assets/{quadrantDiagram-E256RVCF-Ckf5RsLj.js → quadrantDiagram-E256RVCF-CNmFSP0m.js} +1 -1
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-CHxbGrvk.js +1 -0
- package/web/assets/{requirementDiagram-M5DCFWZL-OMiwzJCW.js → requirementDiagram-M5DCFWZL-C6e8zmrF.js} +1 -1
- package/web/assets/{sankeyDiagram-L3NBLAOT-Bu3q2oR9.js → sankeyDiagram-L3NBLAOT-BDcHIgVE.js} +1 -1
- package/web/assets/{sequenceDiagram-ZOUHS735-CCaepW8A.js → sequenceDiagram-ZOUHS735-CGAlp5GL.js} +1 -1
- package/web/assets/{stateDiagram-MLPALWAM-D0pPSUeb.js → stateDiagram-MLPALWAM-CCKt0fuW.js} +1 -1
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-OC7lFsht.js +1 -0
- package/web/assets/{timeline-definition-5SPVSISX-Bnt0tSZA.js → timeline-definition-5SPVSISX-D74jeyVQ.js} +1 -1
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-8EO9YZIQ.js +1 -0
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-y8YnclBi.js +1 -0
- package/web/assets/{vennDiagram-IE5QUKF5-D1QPXqvM.js → vennDiagram-IE5QUKF5-ChHw0kJO.js} +1 -1
- package/web/assets/wardley-RL74JXVD-BCRCBASE-1QZagPhJ.js +1 -0
- package/web/assets/{wardleyDiagram-XU3VSMPF-DgVu-8V0.js → wardleyDiagram-XU3VSMPF-D5PsSVSp.js} +1 -1
- package/web/assets/{xychartDiagram-ZHJ5623Y-DY2IlISC.js → xychartDiagram-ZHJ5623Y-B7E396Z-.js} +1 -1
- package/web/index.html +1 -1
- package/web/assets/architecture-YZFGNWBL-S5CXDPWN-DJAeZ1fZ.js +0 -1
- package/web/assets/classDiagram-PPOCWD7C-CaCJnkFe.js +0 -1
- package/web/assets/classDiagram-v2-23LJLIIU-wdvhnHkq.js +0 -1
- package/web/assets/gitGraph-7Q5UKJZL-54BCDZD5-DkYkEmdA.js +0 -1
- package/web/assets/info-OMHHGYJF-BF2H5H6G-C3e2WXUC.js +0 -1
- package/web/assets/infoDiagram-MN7RKWGX-CIHfQ_Ot.js +0 -2
- package/web/assets/packet-4T2RLAQJ-EV4IVRXR-_hVXeTya.js +0 -1
- package/web/assets/pie-ZZUOXDRM-N23DN5KN-Cwk3Ms79.js +0 -1
- package/web/assets/radar-PYXPWWZC-P6TP7ZYP-BuQ3fcO-.js +0 -1
- package/web/assets/stateDiagram-v2-B5LQ5ZB2-DQ0mfMOw.js +0 -1
- package/web/assets/treeView-SZITEDCU-5DXDK3XO-B2JHGeuB.js +0 -1
- package/web/assets/treemap-W4RFUUIX-WYLRDWKO-BKeOEBXU.js +0 -1
- package/web/assets/wardley-RL74JXVD-BCRCBASE-DLAazngR.js +0 -1
package/dist/cli/analyze.js
CHANGED
|
@@ -16,6 +16,8 @@ import { getStoragePaths, getGlobalRegistryPath, RegistryNameCollisionError, Ana
|
|
|
16
16
|
import { getGitRoot, hasGitDir } from '../storage/git.js';
|
|
17
17
|
import { runFullAnalysis } from '../core/run-analyze.js';
|
|
18
18
|
import { getMaxFileSizeBannerMessage } from '../core/ingestion/utils/max-file-size.js';
|
|
19
|
+
import { warnMissingOptionalGrammars } from './optional-grammars.js';
|
|
20
|
+
import { glob } from 'glob';
|
|
19
21
|
import fs from 'fs/promises';
|
|
20
22
|
// Capture stderr.write at module load BEFORE anything (LadybugDB native
|
|
21
23
|
// init, progress bar, console redirection) can monkey-patch it. The
|
|
@@ -173,6 +175,31 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
173
175
|
if (!repoHasGit) {
|
|
174
176
|
console.log(' Warning: no .git directory found \u2014 commit-tracking and incremental updates disabled.\n');
|
|
175
177
|
}
|
|
178
|
+
// If the target repo contains files an optional grammar would parse but
|
|
179
|
+
// that grammar's native binding is absent, warn before analysis so users
|
|
180
|
+
// learn why those files end up unparsed instead of silently getting a
|
|
181
|
+
// degraded index.
|
|
182
|
+
try {
|
|
183
|
+
const matches = await glob(['**/*.dart', '**/*.proto'], {
|
|
184
|
+
cwd: repoPath,
|
|
185
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
|
|
186
|
+
dot: false,
|
|
187
|
+
nodir: true,
|
|
188
|
+
absolute: false,
|
|
189
|
+
});
|
|
190
|
+
if (matches.length > 0) {
|
|
191
|
+
const present = new Set();
|
|
192
|
+
for (const m of matches) {
|
|
193
|
+
const ext = path.extname(m).toLowerCase();
|
|
194
|
+
if (ext)
|
|
195
|
+
present.add(ext);
|
|
196
|
+
}
|
|
197
|
+
warnMissingOptionalGrammars({ context: 'analyze', relevantExtensions: present });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Best-effort warning \u2014 never block analyze on the precheck.
|
|
202
|
+
}
|
|
176
203
|
// KuzuDB migration cleanup is handled by runFullAnalysis internally.
|
|
177
204
|
// Note: --skills is handled after runFullAnalysis using the returned pipelineResult.
|
|
178
205
|
if (process.env.GITNEXUS_NO_GITIGNORE) {
|
package/dist/cli/mcp.d.ts
CHANGED
|
@@ -4,5 +4,26 @@
|
|
|
4
4
|
* Starts the MCP server in standalone mode.
|
|
5
5
|
* Loads all indexed repos from the global registry.
|
|
6
6
|
* No longer depends on cwd — works from any directory.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: this module's static-import closure is intentionally tiny
|
|
9
|
+
* (one chain: `mcp/stdio-context.js` → `mcp/stdio-capture.js`, which is a
|
|
10
|
+
* leaf with zero non-`node:` imports). All heavy backend modules
|
|
11
|
+
* (`startMCPServer`, `LocalBackend`, `warnMissingOptionalGrammars`) load
|
|
12
|
+
* via `await import(...)` AFTER `installGlobalStdoutSentinel()` runs.
|
|
13
|
+
*
|
|
14
|
+
* This closes the ESM-evaluation-order window where native init banners
|
|
15
|
+
* from `@ladybugdb/core` (or any future heavy import) could reach raw
|
|
16
|
+
* stdout before the sentinel exists. Codex's adversarial review on
|
|
17
|
+
* PR #1383 found that even with the sentinel-install call as the first
|
|
18
|
+
* statement of `mcpCommand`, ESM evaluates static imports of THIS module
|
|
19
|
+
* before the function body runs — so any native side effects during
|
|
20
|
+
* those imports happen before the sentinel can intercept them.
|
|
21
|
+
*
|
|
22
|
+
* If you find yourself adding a static `import` to this file, ask
|
|
23
|
+
* whether the imported module (or anything it transitively imports)
|
|
24
|
+
* touches `process.stdout` or loads a native binding at module init. If
|
|
25
|
+
* either is true, switch it to a dynamic `await import(...)` inside
|
|
26
|
+
* `mcpCommand` after the sentinel install. The regression test at
|
|
27
|
+
* `gitnexus/test/integration/mcp/import-closure.test.ts` enforces this.
|
|
7
28
|
*/
|
|
8
29
|
export declare const mcpCommand: () => Promise<void>;
|
package/dist/cli/mcp.js
CHANGED
|
@@ -4,21 +4,50 @@
|
|
|
4
4
|
* Starts the MCP server in standalone mode.
|
|
5
5
|
* Loads all indexed repos from the global registry.
|
|
6
6
|
* No longer depends on cwd — works from any directory.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: this module's static-import closure is intentionally tiny
|
|
9
|
+
* (one chain: `mcp/stdio-context.js` → `mcp/stdio-capture.js`, which is a
|
|
10
|
+
* leaf with zero non-`node:` imports). All heavy backend modules
|
|
11
|
+
* (`startMCPServer`, `LocalBackend`, `warnMissingOptionalGrammars`) load
|
|
12
|
+
* via `await import(...)` AFTER `installGlobalStdoutSentinel()` runs.
|
|
13
|
+
*
|
|
14
|
+
* This closes the ESM-evaluation-order window where native init banners
|
|
15
|
+
* from `@ladybugdb/core` (or any future heavy import) could reach raw
|
|
16
|
+
* stdout before the sentinel exists. Codex's adversarial review on
|
|
17
|
+
* PR #1383 found that even with the sentinel-install call as the first
|
|
18
|
+
* statement of `mcpCommand`, ESM evaluates static imports of THIS module
|
|
19
|
+
* before the function body runs — so any native side effects during
|
|
20
|
+
* those imports happen before the sentinel can intercept them.
|
|
21
|
+
*
|
|
22
|
+
* If you find yourself adding a static `import` to this file, ask
|
|
23
|
+
* whether the imported module (or anything it transitively imports)
|
|
24
|
+
* touches `process.stdout` or loads a native binding at module init. If
|
|
25
|
+
* either is true, switch it to a dynamic `await import(...)` inside
|
|
26
|
+
* `mcpCommand` after the sentinel install. The regression test at
|
|
27
|
+
* `gitnexus/test/integration/mcp/import-closure.test.ts` enforces this.
|
|
7
28
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
29
|
+
import { installGlobalStdoutSentinel } from '../mcp/stdio-context.js';
|
|
10
30
|
export const mcpCommand = async () => {
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
// Install the global stdout sentinel as the very first thing — before
|
|
32
|
+
// ANY other module loads. The static-import closure above is leaf-only
|
|
33
|
+
// (stdio-context → stdio-capture, zero non-`node:` deps), so this is
|
|
34
|
+
// also the first chance any code in this process has to write to stdout.
|
|
35
|
+
installGlobalStdoutSentinel();
|
|
36
|
+
// uncaughtException/unhandledRejection handlers are owned by
|
|
37
|
+
// startMCPServer (gitnexus/src/mcp/server.ts) so the server's shutdown
|
|
38
|
+
// path runs cleanly with full stack traces. Registering duplicates here
|
|
39
|
+
// would only produce noisy double-logging on the same exception.
|
|
40
|
+
// Now safe to dynamically import the heavy backend modules. Anything
|
|
41
|
+
// they emit to stdout during evaluation will route through the sentinel.
|
|
42
|
+
const [{ startMCPServer }, { LocalBackend }] = await Promise.all([
|
|
43
|
+
import('../mcp/server.js'),
|
|
44
|
+
import('../mcp/local/local-backend.js'),
|
|
45
|
+
]);
|
|
46
|
+
// Missing-optional-grammar warnings are intentionally NOT emitted here.
|
|
47
|
+
// `gitnexus analyze` already warns at index time, filtered by the repo's
|
|
48
|
+
// actual extensions, and a repo can only be served by MCP after analyze
|
|
49
|
+
// has run. Repeating an unconditional warning at every MCP startup is
|
|
50
|
+
// pure noise for users whose indexed repos don't use Dart/Proto.
|
|
22
51
|
// Initialize multi-repo backend from registry.
|
|
23
52
|
// The server starts even with 0 repos — tools call refreshRepos() lazily,
|
|
24
53
|
// so repos indexed after the server starts are discovered automatically.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional grammar availability check.
|
|
3
|
+
*
|
|
4
|
+
* tree-sitter-dart and tree-sitter-proto are optionalDependencies that
|
|
5
|
+
* require a `node-gyp rebuild` at install time. The build can be skipped
|
|
6
|
+
* via GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or it can
|
|
7
|
+
* silently soft-fail when the C++ toolchain is missing.
|
|
8
|
+
*
|
|
9
|
+
* Either path produces the same observable: the .node binding is absent
|
|
10
|
+
* at runtime. This helper detects that condition and surfaces a single
|
|
11
|
+
* stderr line per missing grammar so users learn why .dart/.proto support
|
|
12
|
+
* is unavailable instead of silently getting a degraded index.
|
|
13
|
+
*/
|
|
14
|
+
export interface MissingGrammar {
|
|
15
|
+
name: string;
|
|
16
|
+
extensions: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the list of optional grammars whose native binding cannot be
|
|
20
|
+
* loaded. Actually `require()`s the package — `require.resolve` would
|
|
21
|
+
* locate the entry path even when the `.node` binding is absent (the
|
|
22
|
+
* `file:` package directory is installed regardless of postinstall
|
|
23
|
+
* outcome), giving false negatives for the exact users we want to warn:
|
|
24
|
+
* those who installed with `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` or whose
|
|
25
|
+
* native rebuild soft-failed for missing toolchain.
|
|
26
|
+
*
|
|
27
|
+
* Node's module cache memoizes `require()` for us — calling this multiple
|
|
28
|
+
* times is cheap. The catch distinguishes "missing" (MODULE_NOT_FOUND or
|
|
29
|
+
* the typical node-gyp-build "could not find any binding" pattern) from
|
|
30
|
+
* "broken" (SyntaxError, EACCES, native crash). Broken bindings surface a
|
|
31
|
+
* separate stderr line so users get an actionable message instead of a
|
|
32
|
+
* misleading "reinstall" hint.
|
|
33
|
+
*/
|
|
34
|
+
export declare function detectMissingOptionalGrammars(): MissingGrammar[];
|
|
35
|
+
/**
|
|
36
|
+
* Log a one-line stderr warning for each missing grammar. Safe to call
|
|
37
|
+
* unconditionally — silent if all grammars are present.
|
|
38
|
+
*
|
|
39
|
+
* `relevantExtensions`, if provided, filters the warning to grammars whose
|
|
40
|
+
* extensions appear in the set (e.g. an analyze run can pass the set of
|
|
41
|
+
* extensions actually present in the target repo so users without any
|
|
42
|
+
* .dart/.proto files don't see noise).
|
|
43
|
+
*/
|
|
44
|
+
export declare function warnMissingOptionalGrammars(opts?: {
|
|
45
|
+
context?: string;
|
|
46
|
+
relevantExtensions?: ReadonlySet<string>;
|
|
47
|
+
}): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional grammar availability check.
|
|
3
|
+
*
|
|
4
|
+
* tree-sitter-dart and tree-sitter-proto are optionalDependencies that
|
|
5
|
+
* require a `node-gyp rebuild` at install time. The build can be skipped
|
|
6
|
+
* via GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (postinstall scripts), or it can
|
|
7
|
+
* silently soft-fail when the C++ toolchain is missing.
|
|
8
|
+
*
|
|
9
|
+
* Either path produces the same observable: the .node binding is absent
|
|
10
|
+
* at runtime. This helper detects that condition and surfaces a single
|
|
11
|
+
* stderr line per missing grammar so users learn why .dart/.proto support
|
|
12
|
+
* is unavailable instead of silently getting a degraded index.
|
|
13
|
+
*/
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
const _require = createRequire(import.meta.url);
|
|
16
|
+
const OPTIONAL_GRAMMARS = [
|
|
17
|
+
{ name: 'tree-sitter-dart', pkg: 'tree-sitter-dart', extensions: ['.dart'] },
|
|
18
|
+
{ name: 'tree-sitter-proto', pkg: 'tree-sitter-proto', extensions: ['.proto'] },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Returns the list of optional grammars whose native binding cannot be
|
|
22
|
+
* loaded. Actually `require()`s the package — `require.resolve` would
|
|
23
|
+
* locate the entry path even when the `.node` binding is absent (the
|
|
24
|
+
* `file:` package directory is installed regardless of postinstall
|
|
25
|
+
* outcome), giving false negatives for the exact users we want to warn:
|
|
26
|
+
* those who installed with `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` or whose
|
|
27
|
+
* native rebuild soft-failed for missing toolchain.
|
|
28
|
+
*
|
|
29
|
+
* Node's module cache memoizes `require()` for us — calling this multiple
|
|
30
|
+
* times is cheap. The catch distinguishes "missing" (MODULE_NOT_FOUND or
|
|
31
|
+
* the typical node-gyp-build "could not find any binding" pattern) from
|
|
32
|
+
* "broken" (SyntaxError, EACCES, native crash). Broken bindings surface a
|
|
33
|
+
* separate stderr line so users get an actionable message instead of a
|
|
34
|
+
* misleading "reinstall" hint.
|
|
35
|
+
*/
|
|
36
|
+
export function detectMissingOptionalGrammars() {
|
|
37
|
+
const missing = [];
|
|
38
|
+
for (const g of OPTIONAL_GRAMMARS) {
|
|
39
|
+
try {
|
|
40
|
+
_require(g.pkg);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const code = err?.code;
|
|
44
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
45
|
+
const looksMissing = code === 'MODULE_NOT_FOUND' ||
|
|
46
|
+
code === 'ERR_MODULE_NOT_FOUND' ||
|
|
47
|
+
/could not find|no native build|prebuilds/i.test(msg);
|
|
48
|
+
if (!looksMissing) {
|
|
49
|
+
// Present but broken — surface so the user doesn't get a misleading
|
|
50
|
+
// "reinstall" recovery message that wouldn't actually help.
|
|
51
|
+
console.error(`GitNexus: optional grammar "${g.name}" is installed but failed to load (${msg.slice(0, 200)}). ${g.extensions.join('/')} files will not be parsed.`);
|
|
52
|
+
}
|
|
53
|
+
missing.push({ name: g.name, extensions: g.extensions });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return missing;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Log a one-line stderr warning for each missing grammar. Safe to call
|
|
60
|
+
* unconditionally — silent if all grammars are present.
|
|
61
|
+
*
|
|
62
|
+
* `relevantExtensions`, if provided, filters the warning to grammars whose
|
|
63
|
+
* extensions appear in the set (e.g. an analyze run can pass the set of
|
|
64
|
+
* extensions actually present in the target repo so users without any
|
|
65
|
+
* .dart/.proto files don't see noise).
|
|
66
|
+
*/
|
|
67
|
+
export function warnMissingOptionalGrammars(opts) {
|
|
68
|
+
const missing = detectMissingOptionalGrammars();
|
|
69
|
+
if (missing.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
const ctx = opts?.context ? ` [${opts.context}]` : '';
|
|
72
|
+
for (const g of missing) {
|
|
73
|
+
if (opts?.relevantExtensions && !g.extensions.some((e) => opts.relevantExtensions.has(e))) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
console.error(`GitNexus${ctx}: optional grammar "${g.name}" is unavailable — ${g.extensions.join('/')} files will not be parsed. Reinstall without GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1 (and ensure python3, make, g++) to enable.`);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/cli/setup.js
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'fs/promises';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import os from 'os';
|
|
11
11
|
import { execFile, execFileSync } from 'child_process';
|
|
12
|
+
import { createRequire } from 'module';
|
|
12
13
|
import { promisify } from 'util';
|
|
13
14
|
import { fileURLToPath } from 'url';
|
|
14
15
|
import { glob } from 'glob';
|
|
@@ -17,6 +18,18 @@ import { getGlobalDir } from '../storage/repo-manager.js';
|
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = path.dirname(__filename);
|
|
19
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
+
// Pin the npx fallback to the installed version. Reason: setup.ts writes
|
|
22
|
+
// a config that persists in the user's editor and is invoked on every MCP
|
|
23
|
+
// connect. Pinning to the installed version means subsequent invocations
|
|
24
|
+
// skip the npm-registry metadata roundtrip (and stay reproducible until
|
|
25
|
+
// the user upgrades). Static configs and READMEs intentionally use
|
|
26
|
+
// `gitnexus@latest` since they're quickstart docs, not persisted state.
|
|
27
|
+
const _require = createRequire(import.meta.url);
|
|
28
|
+
const _pkg = _require('../../package.json');
|
|
29
|
+
if (typeof _pkg.version !== 'string' || !_pkg.version) {
|
|
30
|
+
throw new Error('gitnexus/package.json#version is missing or not a string — cannot generate MCP fallback config.');
|
|
31
|
+
}
|
|
32
|
+
const NPX_REF = `gitnexus@${_pkg.version}`;
|
|
20
33
|
/**
|
|
21
34
|
* Resolve the absolute path to the `gitnexus` binary if it's installed
|
|
22
35
|
* globally (or via npm -g / yarn global). Returns null when not found.
|
|
@@ -51,8 +64,10 @@ function resolveGitnexusBin() {
|
|
|
51
64
|
* The MCP server entry for all editors.
|
|
52
65
|
*
|
|
53
66
|
* Prefers the globally-installed `gitnexus` binary (starts in ~1 s) over
|
|
54
|
-
* `npx -y gitnexus
|
|
55
|
-
* >60 s, exceeding Claude Code's 30 s MCP connection timeout).
|
|
67
|
+
* `npx -y gitnexus@<version>` (cold-cache install of native deps can take
|
|
68
|
+
* >60 s, exceeding Claude Code's 30 s MCP connection timeout). The fallback
|
|
69
|
+
* version is read from gitnexus/package.json#version at module load so the
|
|
70
|
+
* persisted user config matches the installed package.
|
|
56
71
|
*
|
|
57
72
|
* Falls back to npx when the binary isn't on PATH — e.g. first-time
|
|
58
73
|
* users who ran `npx gitnexus analyze` but haven't done `npm i -g`.
|
|
@@ -66,12 +81,12 @@ function getMcpEntry() {
|
|
|
66
81
|
if (process.platform === 'win32') {
|
|
67
82
|
return {
|
|
68
83
|
command: 'cmd',
|
|
69
|
-
args: ['/c', 'npx', '-y',
|
|
84
|
+
args: ['/c', 'npx', '-y', NPX_REF, 'mcp'],
|
|
70
85
|
};
|
|
71
86
|
}
|
|
72
87
|
return {
|
|
73
88
|
command: 'npx',
|
|
74
|
-
args: ['-y',
|
|
89
|
+
args: ['-y', NPX_REF, 'mcp'],
|
|
75
90
|
};
|
|
76
91
|
}
|
|
77
92
|
/**
|
|
@@ -84,9 +99,9 @@ function getOpenCodeMcpEntry() {
|
|
|
84
99
|
return { type: 'local', command: [bin, 'mcp'] };
|
|
85
100
|
}
|
|
86
101
|
if (process.platform === 'win32') {
|
|
87
|
-
return { type: 'local', command: ['cmd', '/c', 'npx', '-y',
|
|
102
|
+
return { type: 'local', command: ['cmd', '/c', 'npx', '-y', NPX_REF, 'mcp'] };
|
|
88
103
|
}
|
|
89
|
-
return { type: 'local', command: ['npx', '-y',
|
|
104
|
+
return { type: 'local', command: ['npx', '-y', NPX_REF, 'mcp'] };
|
|
90
105
|
}
|
|
91
106
|
/**
|
|
92
107
|
* Detect indentation style from file content.
|
|
@@ -139,7 +139,7 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
|
|
|
139
139
|
applyHfEnvOverrides(env);
|
|
140
140
|
const isDev = process.env.NODE_ENV === 'development';
|
|
141
141
|
if (isDev) {
|
|
142
|
-
console.
|
|
142
|
+
console.error(`🧠 Loading embedding model: ${finalConfig.modelId}`);
|
|
143
143
|
}
|
|
144
144
|
const progressCallback = onProgress
|
|
145
145
|
? (data) => {
|
|
@@ -161,16 +161,16 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
|
|
|
161
161
|
for (const device of devicesToTry) {
|
|
162
162
|
try {
|
|
163
163
|
if (isDev && device === 'dml') {
|
|
164
|
-
console.
|
|
164
|
+
console.error('🔧 Trying DirectML (DirectX12) GPU backend...');
|
|
165
165
|
}
|
|
166
166
|
else if (isDev && device === 'cuda') {
|
|
167
|
-
console.
|
|
167
|
+
console.error('🔧 Trying CUDA GPU backend...');
|
|
168
168
|
}
|
|
169
169
|
else if (isDev && device === 'cpu') {
|
|
170
|
-
console.
|
|
170
|
+
console.error('🔧 Using CPU backend...');
|
|
171
171
|
}
|
|
172
172
|
else if (isDev && device === 'wasm') {
|
|
173
|
-
console.
|
|
173
|
+
console.error('🔧 Using WASM backend (slower)...');
|
|
174
174
|
}
|
|
175
175
|
embedderInstance = await pipeline('feature-extraction', finalConfig.modelId, {
|
|
176
176
|
device: device,
|
|
@@ -190,15 +190,15 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
|
|
|
190
190
|
: device === 'cuda'
|
|
191
191
|
? 'GPU (CUDA)'
|
|
192
192
|
: device.toUpperCase();
|
|
193
|
-
console.
|
|
194
|
-
console.
|
|
193
|
+
console.error(`✅ Using ${label} backend`);
|
|
194
|
+
console.error('✅ Embedding model loaded successfully');
|
|
195
195
|
}
|
|
196
196
|
return embedderInstance;
|
|
197
197
|
}
|
|
198
198
|
catch (deviceError) {
|
|
199
199
|
if (isDev && (device === 'cuda' || device === 'dml')) {
|
|
200
200
|
const gpuType = device === 'dml' ? 'DirectML' : 'CUDA';
|
|
201
|
-
console.
|
|
201
|
+
console.error(`⚠️ ${gpuType} not available, falling back to CPU...`);
|
|
202
202
|
}
|
|
203
203
|
// Continue to next device in list
|
|
204
204
|
if (device === devicesToTry[devicesToTry.length - 1]) {
|
|
@@ -112,7 +112,7 @@ const queryEmbeddableNodes = async (executeQuery) => {
|
|
|
112
112
|
}
|
|
113
113
|
catch (error) {
|
|
114
114
|
if (isDev) {
|
|
115
|
-
console.
|
|
115
|
+
console.error(`Query for ${label} nodes failed:`, error);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -151,7 +151,7 @@ const createVectorIndex = async (executeQuery) => {
|
|
|
151
151
|
}
|
|
152
152
|
catch (error) {
|
|
153
153
|
if (isDev) {
|
|
154
|
-
console.
|
|
154
|
+
console.error('Vector index creation warning:', error);
|
|
155
155
|
}
|
|
156
156
|
return false;
|
|
157
157
|
}
|
|
@@ -176,7 +176,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
176
176
|
try {
|
|
177
177
|
const vectorAvailable = await ensureVectorExtensionAvailable();
|
|
178
178
|
if (!vectorAvailable && isDev)
|
|
179
|
-
console.
|
|
179
|
+
console.error(vectorUnavailableMessage);
|
|
180
180
|
// Phase 1: Load embedding model
|
|
181
181
|
onProgress({
|
|
182
182
|
phase: 'loading-model',
|
|
@@ -199,7 +199,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
199
199
|
modelDownloadPercent: 100,
|
|
200
200
|
});
|
|
201
201
|
if (isDev) {
|
|
202
|
-
console.
|
|
202
|
+
console.error('🔍 Querying embeddable nodes...');
|
|
203
203
|
}
|
|
204
204
|
// Phase 2: Query embeddable nodes
|
|
205
205
|
let nodes = await queryEmbeddableNodes(executeQuery);
|
|
@@ -237,7 +237,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
237
237
|
// (Kuzu forbids SET on vector-indexed properties; DELETE-then-INSERT is the sanctioned pattern)
|
|
238
238
|
if (staleNodeIds.length > 0) {
|
|
239
239
|
if (isDev) {
|
|
240
|
-
console.
|
|
240
|
+
console.error(`🔄 Deleting ${staleNodeIds.length} stale embedding rows for re-embed`);
|
|
241
241
|
}
|
|
242
242
|
try {
|
|
243
243
|
await executeWithReusedStatement(`MATCH (e:${EMBEDDING_TABLE_NAME} {nodeId: $nodeId}) DELETE e`, staleNodeIds.map((nodeId) => ({ nodeId })));
|
|
@@ -253,12 +253,12 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
if (isDev) {
|
|
256
|
-
console.
|
|
256
|
+
console.error(`📦 Incremental embeddings: ${beforeCount} total, ${existingEmbeddings.size} cached, ${staleNodeIds.length} stale, ${nodes.length} to embed`);
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
const totalNodes = nodes.length;
|
|
260
260
|
if (isDev) {
|
|
261
|
-
console.
|
|
261
|
+
console.error(`📊 Found ${totalNodes} embeddable nodes`);
|
|
262
262
|
}
|
|
263
263
|
if (totalNodes === 0) {
|
|
264
264
|
// Ensure the vector index exists even when no new nodes need embedding.
|
|
@@ -324,7 +324,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
324
324
|
}
|
|
325
325
|
catch (chunkErr) {
|
|
326
326
|
if (isDev) {
|
|
327
|
-
console.
|
|
327
|
+
console.error(`⚠️ AST chunking failed for ${node.label} "${node.name}" (${node.filePath}), falling back to character-based chunking:`, chunkErr);
|
|
328
328
|
}
|
|
329
329
|
chunks = characterChunk(node.content, startLine, endLine, chunkSize, overlap);
|
|
330
330
|
}
|
|
@@ -382,7 +382,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
382
382
|
totalNodes,
|
|
383
383
|
});
|
|
384
384
|
if (isDev) {
|
|
385
|
-
console.
|
|
385
|
+
console.error('📇 Creating vector index...');
|
|
386
386
|
}
|
|
387
387
|
const vectorIndexReady = await createVectorIndex(executeQuery);
|
|
388
388
|
onProgress({
|
|
@@ -392,7 +392,7 @@ export const runEmbeddingPipeline = async (executeQuery, executeWithReusedStatem
|
|
|
392
392
|
totalNodes,
|
|
393
393
|
});
|
|
394
394
|
if (isDev) {
|
|
395
|
-
console.
|
|
395
|
+
console.error(`✅ Embedding pipeline complete! (${totalChunks} chunks from ${totalNodes} nodes)`);
|
|
396
396
|
}
|
|
397
397
|
return {
|
|
398
398
|
nodesProcessed: totalNodes,
|
|
@@ -125,7 +125,7 @@ export class ExtensionManager {
|
|
|
125
125
|
}
|
|
126
126
|
const policy = opts.policy ?? this.options.policy ?? resolvePolicyFromEnv();
|
|
127
127
|
const timeoutMs = opts.installTimeoutMs ?? this.options.installTimeoutMs ?? getExtensionInstallTimeoutMs();
|
|
128
|
-
const warn = this.options.warn ?? console.
|
|
128
|
+
const warn = this.options.warn ?? console.error;
|
|
129
129
|
if (policy === 'never') {
|
|
130
130
|
this.markUnavailable(name, label, 'extension install policy is "never"', warn);
|
|
131
131
|
return false;
|
|
@@ -274,7 +274,7 @@ const doInitLbug = async (dbPath) => {
|
|
|
274
274
|
catch (err) {
|
|
275
275
|
const msg = err instanceof Error ? err.message : String(err);
|
|
276
276
|
if (!msg.includes('already exists')) {
|
|
277
|
-
console.
|
|
277
|
+
console.error(`[gitnexus:lbug] schema creation warning: ${msg.slice(0, 120)}`);
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
}
|
|
@@ -889,13 +889,13 @@ export const fetchExistingEmbeddingHashes = async (execQuery) => {
|
|
|
889
889
|
if (nodeId)
|
|
890
890
|
map.set(nodeId, STALE_HASH_SENTINEL);
|
|
891
891
|
}
|
|
892
|
-
console.
|
|
892
|
+
console.error(`[gitnexus:embed] ${map.size} nodes in legacy DB (missing chunk-aware columns) — all treated as stale`);
|
|
893
893
|
return map;
|
|
894
894
|
}
|
|
895
895
|
catch (fallbackErr) {
|
|
896
896
|
const fallbackMsg = fallbackErr?.message ?? '';
|
|
897
897
|
if (isMissingColumnOrTableError(fallbackMsg)) {
|
|
898
|
-
console.
|
|
898
|
+
console.error(`[gitnexus:embed] CodeEmbedding table not yet present — full embedding run (${fallbackMsg})`);
|
|
899
899
|
return undefined;
|
|
900
900
|
}
|
|
901
901
|
throw fallbackErr;
|
|
@@ -31,9 +31,7 @@ type PoolCloseListener = (repoId: string) => void;
|
|
|
31
31
|
* listener (handy for tests).
|
|
32
32
|
*/
|
|
33
33
|
export declare function addPoolCloseListener(listener: PoolCloseListener): () => void;
|
|
34
|
-
|
|
35
|
-
export declare const realStdoutWrite: any;
|
|
36
|
-
export declare const realStderrWrite: any;
|
|
34
|
+
export { realStdoutWrite, realStderrWrite, setActiveStdoutWrite } from '../../mcp/stdio-capture.js';
|
|
37
35
|
/**
|
|
38
36
|
* Touch a repo to reset its idle timeout.
|
|
39
37
|
* Call this during long-running operations to prevent the connection from being closed.
|
|
@@ -90,4 +88,3 @@ export declare const isLbugReady: (repoId: string) => boolean;
|
|
|
90
88
|
export declare const CYPHER_WRITE_RE: RegExp;
|
|
91
89
|
/** Check if a Cypher query contains write operations */
|
|
92
90
|
export declare function isWriteQuery(query: string): boolean;
|
|
93
|
-
export {};
|
|
@@ -38,9 +38,20 @@ const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
38
38
|
/** Max connections per repo (caps concurrent queries per repo) */
|
|
39
39
|
const MAX_CONNS_PER_REPO = 8;
|
|
40
40
|
let idleTimer = null;
|
|
41
|
-
|
|
42
|
-
export
|
|
43
|
-
|
|
41
|
+
// Stdout-capture state lives in `gitnexus/src/mcp/stdio-capture.ts` — a leaf
|
|
42
|
+
// module with zero non-`node:` imports. We re-export the same symbols here
|
|
43
|
+
// so the existing test mock seam (`gitnexus/src/mcp/core/lbug-adapter.ts`
|
|
44
|
+
// re-exports * from this file, and 8+ test files use that path with
|
|
45
|
+
// `vi.mock(...)`) continues to work without churn. The source of truth is
|
|
46
|
+
// the leaf module; this re-export is a compatibility shim.
|
|
47
|
+
//
|
|
48
|
+
// Why the leaf module exists: Codex's adversarial review on PR #1383 found
|
|
49
|
+
// that putting this state in pool-adapter.ts pulled `@ladybugdb/core` into
|
|
50
|
+
// `cli/mcp.ts`'s static-import closure (via stdio-context → pool-adapter →
|
|
51
|
+
// @ladybugdb/core), corrupting stdout in the pre-sentinel window. Routing
|
|
52
|
+
// through the leaf breaks that chain.
|
|
53
|
+
export { realStdoutWrite, realStderrWrite, setActiveStdoutWrite } from '../../mcp/stdio-capture.js';
|
|
54
|
+
import { getActiveStdoutWrite } from '../../mcp/stdio-capture.js';
|
|
44
55
|
let stdoutSilenceCount = 0;
|
|
45
56
|
/** True while pre-warming connections — prevents watchdog from prematurely restoring stdout */
|
|
46
57
|
let preWarmActive = false;
|
|
@@ -155,13 +166,15 @@ let activeQueryCount = 0;
|
|
|
155
166
|
*/
|
|
156
167
|
export function silenceStdout() {
|
|
157
168
|
if (stdoutSilenceCount++ === 0) {
|
|
169
|
+
// eslint-disable-next-line no-restricted-syntax -- silencing infrastructure; replacement is a no-op
|
|
158
170
|
process.stdout.write = (() => true);
|
|
159
171
|
}
|
|
160
172
|
}
|
|
161
173
|
export function restoreStdout() {
|
|
162
174
|
if (--stdoutSilenceCount <= 0) {
|
|
163
175
|
stdoutSilenceCount = 0;
|
|
164
|
-
|
|
176
|
+
// eslint-disable-next-line no-restricted-syntax -- restoring the active stdout-write handler is the silencing API contract
|
|
177
|
+
process.stdout.write = getActiveStdoutWrite();
|
|
165
178
|
}
|
|
166
179
|
}
|
|
167
180
|
// Safety watchdog: restore stdout if it gets stuck silenced (e.g. native crash
|
|
@@ -171,7 +184,8 @@ export function restoreStdout() {
|
|
|
171
184
|
setInterval(() => {
|
|
172
185
|
if (stdoutSilenceCount > 0 && !preWarmActive && activeQueryCount === 0) {
|
|
173
186
|
stdoutSilenceCount = 0;
|
|
174
|
-
|
|
187
|
+
// eslint-disable-next-line no-restricted-syntax -- watchdog recovery for stuck silencing
|
|
188
|
+
process.stdout.write = getActiveStdoutWrite();
|
|
175
189
|
}
|
|
176
190
|
}, 1000).unref();
|
|
177
191
|
function createConnection(db) {
|
|
@@ -114,10 +114,10 @@ const logFailure = (key, result) => {
|
|
|
114
114
|
return;
|
|
115
115
|
logged.add(key);
|
|
116
116
|
const message = `[gitnexus] ${result.note} (${result.error.message})`;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// Both severities go to stderr — console.warn writes to stderr too, but
|
|
118
|
+
// console.error is the stdout-safe channel we standardize on across
|
|
119
|
+
// MCP-reachable code so the ESLint rule covers this directory.
|
|
120
|
+
console.error(message);
|
|
121
121
|
};
|
|
122
122
|
export const resolveLanguageKey = (language, filePath) => language === SupportedLanguages.TypeScript && filePath?.endsWith('.tsx')
|
|
123
123
|
? `${language}:tsx`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { withMcpWrite } from './stdio-context.js';
|
|
3
4
|
function deserializeMessage(raw) {
|
|
4
5
|
return JSONRPCMessageSchema.parse(JSON.parse(raw));
|
|
5
6
|
}
|
|
@@ -185,7 +186,12 @@ export class CompatibleStdioServerTransport {
|
|
|
185
186
|
reject(error);
|
|
186
187
|
};
|
|
187
188
|
this._stdout.on('error', onError);
|
|
188
|
-
|
|
189
|
+
// Tag the write with the MCP transport context so the sentinel
|
|
190
|
+
// (server.ts createStdoutSentinel Proxy) recognizes it as a legitimate
|
|
191
|
+
// JSON-RPC frame and passes it through to the real stdout instead of
|
|
192
|
+
// redirecting to stderr.
|
|
193
|
+
const writeOk = withMcpWrite(() => this._stdout.write(payload));
|
|
194
|
+
if (writeOk) {
|
|
189
195
|
this._stdout.removeListener('error', onError);
|
|
190
196
|
resolve();
|
|
191
197
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LadybugDB connection pool — re-exported from core.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* KEEP THIS FILE. It is intentionally a shim re-export of
|
|
5
|
+
* `../../core/lbug/pool-adapter.js`. The MCP test suite uses this path as
|
|
6
|
+
* a vi.mock seam so unit tests can stub LadybugDB without affecting other
|
|
7
|
+
* importers of `core/lbug/pool-adapter.js` (which is shared with the
|
|
8
|
+
* analyze pipeline). New non-test code MAY import from `pool-adapter.js`
|
|
9
|
+
* directly, but the shim must continue to exist for the mock seam to work.
|
|
4
10
|
*/
|
|
5
11
|
export * from '../../core/lbug/pool-adapter.js';
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LadybugDB connection pool — re-exported from core.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* KEEP THIS FILE. It is intentionally a shim re-export of
|
|
5
|
+
* `../../core/lbug/pool-adapter.js`. The MCP test suite uses this path as
|
|
6
|
+
* a vi.mock seam so unit tests can stub LadybugDB without affecting other
|
|
7
|
+
* importers of `core/lbug/pool-adapter.js` (which is shared with the
|
|
8
|
+
* analyze pipeline). New non-test code MAY import from `pool-adapter.js`
|
|
9
|
+
* directly, but the shim must continue to exist for the mock seam to work.
|
|
4
10
|
*/
|
|
5
11
|
export * from '../../core/lbug/pool-adapter.js';
|