gitnexus 1.6.4 → 1.6.5-rc.10
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 +1 -1
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +30 -12
- package/dist/cli/analyze.d.ts +18 -0
- package/dist/cli/analyze.js +33 -4
- package/dist/cli/augment.d.ts +1 -1
- package/dist/cli/augment.js +1 -1
- package/dist/cli/index.js +6 -1
- package/dist/core/augmentation/engine.d.ts +2 -2
- package/dist/core/augmentation/engine.js +2 -2
- package/dist/core/embeddings/ast-utils.js +2 -1
- package/dist/core/embeddings/http-client.js +22 -6
- package/dist/core/group/extractors/grpc-extractor.js +2 -1
- package/dist/core/group/extractors/http-patterns/python.js +175 -0
- package/dist/core/group/extractors/http-route-extractor.js +2 -1
- package/dist/core/group/extractors/include-extractor.js +2 -1
- package/dist/core/group/extractors/thrift-extractor.js +2 -1
- package/dist/core/group/extractors/tree-sitter-scanner.js +2 -1
- package/dist/core/ingestion/call-processor.js +3 -2
- package/dist/core/ingestion/heritage-processor.js +3 -2
- package/dist/core/ingestion/import-processor.js +2 -1
- package/dist/core/ingestion/languages/c/arity-metadata.d.ts +14 -0
- package/dist/core/ingestion/languages/c/arity-metadata.js +94 -0
- package/dist/core/ingestion/languages/c/arity.d.ts +6 -0
- package/dist/core/ingestion/languages/c/arity.js +18 -0
- package/dist/core/ingestion/languages/c/captures.d.ts +2 -0
- package/dist/core/ingestion/languages/c/captures.js +105 -0
- package/dist/core/ingestion/languages/c/header-scan.d.ts +7 -0
- package/dist/core/ingestion/languages/c/header-scan.js +55 -0
- package/dist/core/ingestion/languages/c/import-decomposer.d.ts +8 -0
- package/dist/core/ingestion/languages/c/import-decomposer.js +50 -0
- package/dist/core/ingestion/languages/c/import-target.d.ts +14 -0
- package/dist/core/ingestion/languages/c/import-target.js +57 -0
- package/dist/core/ingestion/languages/c/index.d.ts +11 -0
- package/dist/core/ingestion/languages/c/index.js +11 -0
- package/dist/core/ingestion/languages/c/interpret.d.ts +14 -0
- package/dist/core/ingestion/languages/c/interpret.js +48 -0
- package/dist/core/ingestion/languages/c/merge-bindings.d.ts +7 -0
- package/dist/core/ingestion/languages/c/merge-bindings.js +23 -0
- package/dist/core/ingestion/languages/c/query.d.ts +3 -0
- package/dist/core/ingestion/languages/c/query.js +161 -0
- package/dist/core/ingestion/languages/c/scope-resolver.d.ts +13 -0
- package/dist/core/ingestion/languages/c/scope-resolver.js +60 -0
- package/dist/core/ingestion/languages/c/simple-hooks.d.ts +14 -0
- package/dist/core/ingestion/languages/c/simple-hooks.js +19 -0
- package/dist/core/ingestion/languages/c/static-linkage.d.ts +13 -0
- package/dist/core/ingestion/languages/c/static-linkage.js +57 -0
- package/dist/core/ingestion/languages/c-cpp.js +10 -0
- package/dist/core/ingestion/languages/csharp/captures.js +2 -1
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +3 -2
- package/dist/core/ingestion/languages/go/captures.js +2 -1
- package/dist/core/ingestion/languages/go/range-binding.js +2 -1
- package/dist/core/ingestion/languages/python/captures.js +2 -1
- package/dist/core/ingestion/languages/typescript/captures.js +2 -1
- package/dist/core/ingestion/parsing-processor.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.js +1 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +11 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +2 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +13 -2
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +4 -1
- package/dist/core/ingestion/workers/parse-worker.js +2 -1
- package/dist/core/lbug/lbug-adapter.js +8 -0
- package/dist/core/lbug/pool-adapter.js +19 -2
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +5 -1
- package/dist/core/tree-sitter/safe-parse.d.ts +6 -0
- package/dist/core/tree-sitter/safe-parse.js +32 -0
- package/dist/server/git-clone.js +10 -13
- package/dist/storage/git.d.ts +17 -6
- package/dist/storage/git.js +46 -12
- package/package.json +1 -1
|
@@ -348,7 +348,18 @@ async function doInitLbug(repoId, dbPath) {
|
|
|
348
348
|
// install; analyze owns extension installation. If LOAD fails, search
|
|
349
349
|
// features degrade gracefully and the user-facing query path proceeds.
|
|
350
350
|
if (!shared.ftsLoaded) {
|
|
351
|
-
|
|
351
|
+
// Windows guard: LOAD EXTENSION fts crashes with SIGSEGV on Windows when
|
|
352
|
+
// the FTS extension binary is not installed locally (@ladybugdb/core native
|
|
353
|
+
// bug — the extension loader hits an unhandled error path that signals SIGSEGV
|
|
354
|
+
// rather than throwing a JS exception, so try/catch cannot protect here).
|
|
355
|
+
// Skip the load on Windows; bm25-index.js catches the resulting Kuzu catalog
|
|
356
|
+
// errors and returns empty BM25 results gracefully. Graph queries are unaffected.
|
|
357
|
+
if (process.platform === 'win32') {
|
|
358
|
+
shared.ftsLoaded = true;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
shared.ftsLoaded = await loadFTSExtension(available[0], { policy: 'load-only' });
|
|
362
|
+
}
|
|
352
363
|
}
|
|
353
364
|
// Register pool entry only after all connections are pre-warmed and FTS is
|
|
354
365
|
// loaded. Concurrent executeQuery calls see either "not initialized"
|
|
@@ -404,8 +415,14 @@ export async function initLbugWithDb(repoId, existingDb, dbPath) {
|
|
|
404
415
|
// Load FTS extension if not already loaded on this Database.
|
|
405
416
|
// policy: 'load-only' — same contract as initLbug above; the read pool
|
|
406
417
|
// must not block on a network install during query execution.
|
|
418
|
+
// Windows guard: same SIGSEGV risk as doInitLbug above — skip on Windows.
|
|
407
419
|
if (!shared.ftsLoaded) {
|
|
408
|
-
|
|
420
|
+
if (process.platform === 'win32') {
|
|
421
|
+
shared.ftsLoaded = true;
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
shared.ftsLoaded = await loadFTSExtension(available[0], { policy: 'load-only' });
|
|
425
|
+
}
|
|
409
426
|
}
|
|
410
427
|
pool.set(repoId, {
|
|
411
428
|
db: existingDb,
|
|
@@ -42,6 +42,8 @@ export interface AnalyzeOptions {
|
|
|
42
42
|
skipAgentsMd?: boolean;
|
|
43
43
|
/** Omit volatile symbol/relationship counts from AGENTS.md and CLAUDE.md. */
|
|
44
44
|
noStats?: boolean;
|
|
45
|
+
/** Skip installing standard GitNexus skill files to .claude/skills/gitnexus/. */
|
|
46
|
+
skipSkills?: boolean;
|
|
45
47
|
/**
|
|
46
48
|
* User-provided alias for the registry `name` (#829). When set,
|
|
47
49
|
* forwarded to `registerRepo` so the indexed repo is stored under
|
package/dist/core/run-analyze.js
CHANGED
|
@@ -350,7 +350,11 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
350
350
|
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
351
351
|
clusters: aggregatedClusterCount,
|
|
352
352
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
353
|
-
}, undefined, {
|
|
353
|
+
}, undefined, {
|
|
354
|
+
skipAgentsMd: options.skipAgentsMd,
|
|
355
|
+
skipSkills: options.skipSkills,
|
|
356
|
+
noStats: options.noStats,
|
|
357
|
+
});
|
|
354
358
|
}
|
|
355
359
|
catch {
|
|
356
360
|
// Best-effort — don't fail the entire analysis for context file issues
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
/**
|
|
3
|
+
* Parse `sourceText` safely on every platform. See {@link SAFE_PARSE_CHUNK_CHARS}
|
|
4
|
+
* for the underlying tree-sitter binding bug this works around.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseSourceSafe(parser: Parser, sourceText: string, oldTree?: Parser.Tree, options?: Parser.Options): Parser.Tree;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tree-sitter 0.21.x's Node native binding crashes (SIGSEGV) on Windows when
|
|
3
|
+
* `parser.parse(string, …)` is handed a JS string longer than 32 767 chars.
|
|
4
|
+
* The crash happens inside the binding's V8 string-to-buffer conversion and
|
|
5
|
+
* cannot be intercepted from JavaScript. The callback (`Parser.Input`) overload
|
|
6
|
+
* pulls source in fixed-size chunks via repeated callback invocations and
|
|
7
|
+
* bypasses that conversion path entirely.
|
|
8
|
+
*
|
|
9
|
+
* Chunk size is comfortably below the boundary; any value < 32 767 works.
|
|
10
|
+
*/
|
|
11
|
+
const SAFE_PARSE_CHUNK_CHARS = 16 * 1024;
|
|
12
|
+
/**
|
|
13
|
+
* Files at or below this length skip the callback machinery and use the
|
|
14
|
+
* direct string overload — the bug only manifests above the int16 boundary,
|
|
15
|
+
* so small inputs save the cost of N callback invocations per parse.
|
|
16
|
+
*/
|
|
17
|
+
const DIRECT_PARSE_LIMIT_CHARS = 16 * 1024;
|
|
18
|
+
/**
|
|
19
|
+
* Parse `sourceText` safely on every platform. See {@link SAFE_PARSE_CHUNK_CHARS}
|
|
20
|
+
* for the underlying tree-sitter binding bug this works around.
|
|
21
|
+
*/
|
|
22
|
+
export function parseSourceSafe(parser, sourceText, oldTree, options) {
|
|
23
|
+
if (sourceText.length <= DIRECT_PARSE_LIMIT_CHARS) {
|
|
24
|
+
return parser.parse(sourceText, oldTree, options);
|
|
25
|
+
}
|
|
26
|
+
const input = (index) => {
|
|
27
|
+
if (index >= sourceText.length)
|
|
28
|
+
return null;
|
|
29
|
+
return sourceText.slice(index, index + SAFE_PARSE_CHUNK_CHARS);
|
|
30
|
+
};
|
|
31
|
+
return parser.parse(input, oldTree, options);
|
|
32
|
+
}
|
package/dist/server/git-clone.js
CHANGED
|
@@ -10,6 +10,7 @@ import os from 'os';
|
|
|
10
10
|
import fs from 'fs/promises';
|
|
11
11
|
import { isIP } from 'net';
|
|
12
12
|
import { logger } from '../core/logger.js';
|
|
13
|
+
import { parseRepoNameFromUrl } from '../storage/git.js';
|
|
13
14
|
/** Root directory for all cloned repositories. Targets must resolve inside this. */
|
|
14
15
|
const CLONE_ROOT = path.resolve(path.join(os.homedir(), '.gitnexus', 'repos'));
|
|
15
16
|
// A valid git repository name is filesystem-safe: alphanumerics plus `. _ -`.
|
|
@@ -25,19 +26,15 @@ const REPO_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
|
25
26
|
* clone root via path traversal.
|
|
26
27
|
*/
|
|
27
28
|
export function extractRepoName(url) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const cleaned = url.slice(0, end);
|
|
35
|
-
const lastSegment = cleaned.split(/[/:]/).pop() || '';
|
|
36
|
-
const stripped = lastSegment.endsWith('.git') ? lastSegment.slice(0, -4) : lastSegment;
|
|
37
|
-
if (!stripped || stripped === '.' || stripped === '..' || !REPO_NAME_PATTERN.test(stripped)) {
|
|
29
|
+
const name = parseRepoNameFromUrl(url);
|
|
30
|
+
if (!name ||
|
|
31
|
+
name === '.' ||
|
|
32
|
+
name === '..' ||
|
|
33
|
+
name === 'unknown' ||
|
|
34
|
+
!REPO_NAME_PATTERN.test(name)) {
|
|
38
35
|
throw new Error('Could not extract a valid repository name from URL');
|
|
39
36
|
}
|
|
40
|
-
return
|
|
37
|
+
return name;
|
|
41
38
|
}
|
|
42
39
|
/** Get the clone target directory for a repo name. */
|
|
43
40
|
export function getCloneDir(repoName) {
|
|
@@ -347,8 +344,8 @@ export async function cloneOrPull(url, targetDir, onProgress) {
|
|
|
347
344
|
throw new Error(`Clone target must be a subdirectory of ${CLONE_ROOT}`);
|
|
348
345
|
}
|
|
349
346
|
// Always validate the requested URL — the prior shape only ran this in
|
|
350
|
-
// the
|
|
351
|
-
//
|
|
347
|
+
// the code path where the repo was cloned. Now it runs unconditionally,
|
|
348
|
+
// preventing SSRF / blocked-host bypasses even when targetDir already exists.
|
|
352
349
|
validateGitUrl(url);
|
|
353
350
|
const exists = await fs.access(path.join(safeTarget, '.git')).then(() => true, () => false);
|
|
354
351
|
if (exists) {
|
package/dist/storage/git.d.ts
CHANGED
|
@@ -108,13 +108,24 @@ export declare const hasGitDir: (dirPath: string) => boolean;
|
|
|
108
108
|
*/
|
|
109
109
|
export declare const getRemoteOriginUrl: (repoPath: string) => string | null;
|
|
110
110
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
|
|
114
|
-
* unparseable input.
|
|
111
|
+
* Sanitize a repository name to prevent argument injection and ensure
|
|
112
|
+
* cross-platform filesystem compatibility.
|
|
115
113
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
114
|
+
* 1. Strips leading dashes to prevent git command-line argument injection
|
|
115
|
+
* (e.g., --upload-pack=evil).
|
|
116
|
+
* 2. Replaces characters that are unsafe for directory names across
|
|
117
|
+
* platforms (Windows/macOS/Linux) with underscores.
|
|
118
|
+
* 3. Blocks path traversal segments ("." and "..") and Windows reserved
|
|
119
|
+
* names (e.g., CON, NUL) to prevent directory escape.
|
|
120
|
+
*/
|
|
121
|
+
export declare const sanitizeRepoName: (name: string) => string;
|
|
122
|
+
/**
|
|
123
|
+
* Parse a repository name out of a git remote URL. Handles common shapes
|
|
124
|
+
* including SSH (git@host:owner/repo.git) and HTTPS (https://host/owner/repo.git).
|
|
125
|
+
*
|
|
126
|
+
* Returns a sanitized, filesystem-safe name or null if no name could be inferred.
|
|
127
|
+
* Returning null (rather than 'unknown') allows callers to use ?? null-coalescing
|
|
128
|
+
* for fallbacks without risk of registry collisions on 'unknown'.
|
|
118
129
|
*/
|
|
119
130
|
export declare const parseRepoNameFromUrl: (url: string | null | undefined) => string | null;
|
|
120
131
|
/**
|
package/dist/storage/git.js
CHANGED
|
@@ -257,13 +257,36 @@ export const getRemoteOriginUrl = (repoPath) => {
|
|
|
257
257
|
}
|
|
258
258
|
};
|
|
259
259
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* `git://`, `ssh://`, and `file://` shapes. Returns `null` for empty /
|
|
263
|
-
* unparseable input.
|
|
260
|
+
* Sanitize a repository name to prevent argument injection and ensure
|
|
261
|
+
* cross-platform filesystem compatibility.
|
|
264
262
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
263
|
+
* 1. Strips leading dashes to prevent git command-line argument injection
|
|
264
|
+
* (e.g., --upload-pack=evil).
|
|
265
|
+
* 2. Replaces characters that are unsafe for directory names across
|
|
266
|
+
* platforms (Windows/macOS/Linux) with underscores.
|
|
267
|
+
* 3. Blocks path traversal segments ("." and "..") and Windows reserved
|
|
268
|
+
* names (e.g., CON, NUL) to prevent directory escape.
|
|
269
|
+
*/
|
|
270
|
+
export const sanitizeRepoName = (name) => {
|
|
271
|
+
// 1. Prevent argument injection by stripping leading dashes.
|
|
272
|
+
// 2. Remove characters that are not alphanumerics, dots, underscores, or dashes.
|
|
273
|
+
const sanitized = name.replace(/^-+/, '').replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
274
|
+
// 3. Block path traversal segments and Windows reserved names.
|
|
275
|
+
// Windows reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9 cannot
|
|
276
|
+
// be used as directory names on Windows even if they have an extension.
|
|
277
|
+
const reserved = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$/i;
|
|
278
|
+
if (!sanitized || sanitized === '.' || sanitized === '..' || reserved.test(sanitized)) {
|
|
279
|
+
return 'unknown';
|
|
280
|
+
}
|
|
281
|
+
return sanitized;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Parse a repository name out of a git remote URL. Handles common shapes
|
|
285
|
+
* including SSH (git@host:owner/repo.git) and HTTPS (https://host/owner/repo.git).
|
|
286
|
+
*
|
|
287
|
+
* Returns a sanitized, filesystem-safe name or null if no name could be inferred.
|
|
288
|
+
* Returning null (rather than 'unknown') allows callers to use ?? null-coalescing
|
|
289
|
+
* for fallbacks without risk of registry collisions on 'unknown'.
|
|
267
290
|
*/
|
|
268
291
|
export const parseRepoNameFromUrl = (url) => {
|
|
269
292
|
if (!url)
|
|
@@ -271,12 +294,23 @@ export const parseRepoNameFromUrl = (url) => {
|
|
|
271
294
|
const trimmed = url.trim();
|
|
272
295
|
if (!trimmed)
|
|
273
296
|
return null;
|
|
274
|
-
// Strip
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
// Strip trailing slashes without a regex to avoid polynomial-ReDoS on
|
|
298
|
+
// pathological inputs like `https://x.com/y` + '/'.repeat(1e6).
|
|
299
|
+
let end = trimmed.length;
|
|
300
|
+
while (end > 0 && trimmed.charCodeAt(end - 1) === 47 /* '/' */)
|
|
301
|
+
end--;
|
|
302
|
+
let cleaned = trimmed.slice(0, end);
|
|
303
|
+
// Strip trailing .git (case-insensitive)
|
|
304
|
+
if (cleaned.toLowerCase().endsWith('.git')) {
|
|
305
|
+
cleaned = cleaned.slice(0, -4);
|
|
306
|
+
}
|
|
307
|
+
// Last path segment, handling colons for SSH URLs and path traversal.
|
|
308
|
+
// Split on both / and : to consistently extract the last part.
|
|
309
|
+
const candidate = cleaned.split(/[/:]/).pop() || '';
|
|
310
|
+
if (!candidate)
|
|
311
|
+
return null;
|
|
312
|
+
const safe = sanitizeRepoName(candidate);
|
|
313
|
+
return safe === 'unknown' ? null : safe;
|
|
280
314
|
};
|
|
281
315
|
/**
|
|
282
316
|
* Convenience wrapper: derive a registry-friendly name from the repo's
|
package/package.json
CHANGED