@dependabit/detector 0.1.1 → 0.1.14
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 +116 -0
- package/dist/detector.d.ts +19 -0
- package/dist/detector.d.ts.map +1 -1
- package/dist/detector.js +214 -8
- package/dist/detector.js.map +1 -1
- package/dist/llm/copilot.d.ts.map +1 -1
- package/dist/llm/copilot.js +12 -8
- package/dist/llm/copilot.js.map +1 -1
- package/dist/llm/prompts.d.ts +1 -1
- package/dist/llm/prompts.d.ts.map +1 -1
- package/dist/llm/prompts.js +10 -1
- package/dist/llm/prompts.js.map +1 -1
- package/dist/parsers/readme.d.ts.map +1 -1
- package/dist/parsers/readme.js +8 -2
- package/dist/parsers/readme.js.map +1 -1
- package/package.json +10 -4
- package/src/detector.ts +257 -9
- package/src/llm/copilot.ts +13 -10
- package/src/llm/prompts.ts +10 -1
- package/src/parsers/readme.ts +8 -2
- package/test/llm/copilot.test.ts +302 -23
- package/tsconfig.tsbuildinfo +1 -1
package/dist/llm/prompts.js
CHANGED
|
@@ -16,11 +16,20 @@ INCLUDE these types of dependencies:
|
|
|
16
16
|
|
|
17
17
|
EXCLUDE these (handled by dependabot):
|
|
18
18
|
- NPM packages in package.json
|
|
19
|
-
- Python packages in requirements.txt
|
|
19
|
+
- Python packages in requirements.txt
|
|
20
20
|
- Rust crates in Cargo.toml
|
|
21
21
|
- Docker images in Dockerfile
|
|
22
22
|
- Any declared package manager dependencies
|
|
23
23
|
|
|
24
|
+
ALSO EXCLUDE (false positives):
|
|
25
|
+
- URLs that reference the repository itself (self-references)
|
|
26
|
+
- Relative file paths (e.g., CONTRIBUTING.md, docs/guide.md, ./src/utils)
|
|
27
|
+
- Placeholder URLs used in documentation examples (example.com, example.org, localhost)
|
|
28
|
+
- Internal documentation links within the same repository
|
|
29
|
+
- URLs with template variables or placeholders (e.g., issues/[NUMBER], user/repo)
|
|
30
|
+
|
|
31
|
+
Only return dependencies with confidence >= 0.7.
|
|
32
|
+
|
|
24
33
|
For each dependency found, provide:
|
|
25
34
|
1. url: The complete URL
|
|
26
35
|
2. name: A descriptive name
|
package/dist/llm/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/llm/prompts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/llm/prompts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgD3B,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;qBAcpB,CAAC;AAEtB,MAAM,UAAU,qBAAqB,CACnC,WAAmB,EACnB,QAAgB,EAChB,OAAe,EACP;IACR,OAAO,yBAAyB,CAAC,OAAO,CAAC,eAAe,EAAE,WAAW,CAAC;SACnE,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC;SAC/B,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAAA,CAClC;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;EAyB5C,CAAC;AAEH,MAAM,UAAU,0BAA0B,CAAC,GAAW,EAAE,OAAe,EAAU;IAC/E,OAAO,8BAA8B,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAAA,CAC3F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readme.d.ts","sourceRoot":"","sources":["../../src/parsers/readme.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,GAAG,UAAU,GAAG,gBAAgB,CAAC;CACvD;
|
|
1
|
+
{"version":3,"file":"readme.d.ts","sourceRoot":"","sources":["../../src/parsers/readme.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,eAAe,GAAG,UAAU,GAAG,gBAAgB,CAAC;CACvD;AAsBD;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,SAAc,GAAG,kBAAkB,EAAE,CAoDzF;AA6DD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,GACd,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAqCzD"}
|
package/dist/parsers/readme.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* README Parser
|
|
3
3
|
* Extracts URLs and references from README and markdown files
|
|
4
4
|
*/
|
|
5
|
-
// Patterns to skip (package managers, CI badges, shields.io)
|
|
5
|
+
// Patterns to skip (package managers, CI badges, shields.io, placeholders)
|
|
6
6
|
const SKIP_PATTERNS = [
|
|
7
7
|
/npmjs\.com\/package/,
|
|
8
8
|
/pypi\.org\/project/,
|
|
@@ -13,7 +13,13 @@ const SKIP_PATTERNS = [
|
|
|
13
13
|
/badge(s)?\..*\.svg/,
|
|
14
14
|
/travis-ci\.(org|com)/,
|
|
15
15
|
/circleci\.com/,
|
|
16
|
-
/github\.com\/.*\/actions
|
|
16
|
+
/github\.com\/.*\/actions/, // GitHub Actions badges
|
|
17
|
+
/example\.com/, // placeholder domain
|
|
18
|
+
/example\.org/, // placeholder domain
|
|
19
|
+
/localhost/, // local dev URLs
|
|
20
|
+
/127\.0\.0\.1/, // loopback address
|
|
21
|
+
/\[.*\]/, // template placeholders (e.g., issues/[NUMBER])
|
|
22
|
+
/github\.com\/user\/repo/ // common placeholder in docs
|
|
17
23
|
];
|
|
18
24
|
/**
|
|
19
25
|
* Parse README content and extract external references
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readme.js","sourceRoot":"","sources":["../../src/parsers/readme.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,
|
|
1
|
+
{"version":3,"file":"readme.js","sourceRoot":"","sources":["../../src/parsers/readme.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dependabit/detector",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/pradeepmouli/dependabit",
|
|
7
|
+
"directory": "packages/detector"
|
|
8
|
+
},
|
|
4
9
|
"description": "LLM-based dependency detection for external resources",
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "dist/index.js",
|
|
@@ -13,12 +18,13 @@
|
|
|
13
18
|
},
|
|
14
19
|
"dependencies": {
|
|
15
20
|
"@actions/core": "^3.0.0",
|
|
21
|
+
"ignore": "^7.0.5",
|
|
16
22
|
"zod": "^4.3.6",
|
|
17
|
-
"@dependabit/github-client": "0.1.
|
|
18
|
-
"@dependabit/manifest": "0.1.
|
|
23
|
+
"@dependabit/github-client": "0.1.13",
|
|
24
|
+
"@dependabit/manifest": "0.1.13"
|
|
19
25
|
},
|
|
20
26
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^25.2.
|
|
27
|
+
"@types/node": "^25.2.3",
|
|
22
28
|
"tsx": "^4.21.0",
|
|
23
29
|
"typescript": "^5.9.3",
|
|
24
30
|
"vitest": "^4.0.18"
|
package/src/detector.ts
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readdir, readFile } from 'node:fs/promises';
|
|
7
|
-
import {
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { dirname, join, relative, resolve, normalize, sep } from 'node:path';
|
|
8
10
|
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import ignore, { type Ignore } from 'ignore';
|
|
9
12
|
import type { LLMProvider } from './llm/client.js';
|
|
10
13
|
import { createClassificationPrompt } from './llm/prompts.js';
|
|
11
14
|
import { parseReadme } from './parsers/readme.js';
|
|
@@ -27,6 +30,9 @@ export interface DetectorOptions {
|
|
|
27
30
|
repoPath: string;
|
|
28
31
|
llmProvider: LLMProvider;
|
|
29
32
|
ignorePatterns?: string[];
|
|
33
|
+
useGitExcludes?: boolean;
|
|
34
|
+
repoOwner?: string;
|
|
35
|
+
repoName?: string;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
export interface DetectionResult {
|
|
@@ -42,17 +48,13 @@ export interface DetectionResult {
|
|
|
42
48
|
|
|
43
49
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
44
50
|
'node_modules',
|
|
45
|
-
'.git',
|
|
46
51
|
'dist',
|
|
47
52
|
'build',
|
|
48
53
|
'target',
|
|
49
54
|
'vendor',
|
|
50
|
-
'.venv',
|
|
51
55
|
'venv',
|
|
52
56
|
'__pycache__',
|
|
53
|
-
'coverage'
|
|
54
|
-
'.next',
|
|
55
|
-
'.nuxt'
|
|
57
|
+
'coverage'
|
|
56
58
|
];
|
|
57
59
|
|
|
58
60
|
/**
|
|
@@ -60,12 +62,27 @@ const DEFAULT_IGNORE_PATTERNS = [
|
|
|
60
62
|
*/
|
|
61
63
|
export class Detector {
|
|
62
64
|
private options: Required<DetectorOptions>;
|
|
65
|
+
private ignoreMatcher: Ignore | null = null;
|
|
66
|
+
private ignoreMatcherLoaded = false;
|
|
67
|
+
private skipUrlPatterns: RegExp[];
|
|
63
68
|
|
|
64
69
|
constructor(options: DetectorOptions) {
|
|
65
70
|
this.options = {
|
|
66
71
|
...options,
|
|
67
|
-
ignorePatterns: options.ignorePatterns || DEFAULT_IGNORE_PATTERNS
|
|
72
|
+
ignorePatterns: options.ignorePatterns || DEFAULT_IGNORE_PATTERNS,
|
|
73
|
+
useGitExcludes: options.useGitExcludes ?? true,
|
|
74
|
+
repoOwner: options.repoOwner || '',
|
|
75
|
+
repoName: options.repoName || ''
|
|
68
76
|
};
|
|
77
|
+
|
|
78
|
+
this.skipUrlPatterns = [
|
|
79
|
+
/example\.com/,
|
|
80
|
+
/example\.org/,
|
|
81
|
+
/localhost/,
|
|
82
|
+
/127\.0\.0\.1/,
|
|
83
|
+
/\[.*\]/, // template placeholders like [NUMBER]
|
|
84
|
+
/github\.com\/user\/repo/ // common placeholder in docs
|
|
85
|
+
];
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
/**
|
|
@@ -222,6 +239,8 @@ Return as JSON with "dependencies" array.`;
|
|
|
222
239
|
const now = new Date().toISOString();
|
|
223
240
|
|
|
224
241
|
for (const [url, data] of allReferences) {
|
|
242
|
+
// Skip entries that will end up with low confidence
|
|
243
|
+
// (entries that can't be typed programmatically and LLM assigns low confidence)
|
|
225
244
|
// Prepare context for potential LLM use
|
|
226
245
|
const context = data.contexts
|
|
227
246
|
.map((c) => `${c.file}${c.line ? `:${c.line}` : ''}: ${c.text}`)
|
|
@@ -259,6 +278,11 @@ Return as JSON with "dependencies" array.`;
|
|
|
259
278
|
typeConfidence = 0.3;
|
|
260
279
|
}
|
|
261
280
|
|
|
281
|
+
// Skip low-confidence entries
|
|
282
|
+
if (typeConfidence < 0.5) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
262
286
|
// Step 5: Try programmatic access method determination
|
|
263
287
|
let accessMethod: AccessMethod | null = this.determineAccessMethod(url);
|
|
264
288
|
|
|
@@ -354,6 +378,27 @@ Return as JSON: {"accessMethod": "...", "confidence": 0.0-1.0}`;
|
|
|
354
378
|
context: { file: string; line?: number; text: string },
|
|
355
379
|
detectionMethod: DetectionMethod
|
|
356
380
|
): void {
|
|
381
|
+
// Skip URLs that don't start with http:// or https://
|
|
382
|
+
if (!/^https?:\/\//.test(url)) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Skip URLs matching skip patterns (placeholders, localhost, etc.)
|
|
387
|
+
if (this.skipUrlPatterns.some((pattern) => pattern.test(url))) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Skip self-references (URLs pointing to the repo itself)
|
|
392
|
+
if (this.options.repoOwner && this.options.repoName) {
|
|
393
|
+
const selfPattern = new RegExp(
|
|
394
|
+
`github\\.com[/:]${this.escapeRegExp(this.options.repoOwner)}/${this.escapeRegExp(this.options.repoName)}(?:/|$|#|\\?)`,
|
|
395
|
+
'i'
|
|
396
|
+
);
|
|
397
|
+
if (selfPattern.test(url)) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
357
402
|
if (!map.has(url)) {
|
|
358
403
|
map.set(url, {
|
|
359
404
|
url,
|
|
@@ -364,6 +409,10 @@ Return as JSON: {"accessMethod": "...", "confidence": 0.0-1.0}`;
|
|
|
364
409
|
map.get(url)!.contexts.push(context);
|
|
365
410
|
}
|
|
366
411
|
|
|
412
|
+
private escapeRegExp(str: string): string {
|
|
413
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
414
|
+
}
|
|
415
|
+
|
|
367
416
|
/**
|
|
368
417
|
* Programmatically determine access method based on URL patterns
|
|
369
418
|
* Returns null if cannot be determined programmatically
|
|
@@ -505,7 +554,7 @@ Return as JSON: {"accessMethod": "...", "confidence": 0.0-1.0}`;
|
|
|
505
554
|
for (const entry of entries) {
|
|
506
555
|
const fullPath = join(dir, entry.name);
|
|
507
556
|
|
|
508
|
-
if (this.
|
|
557
|
+
if (await this.shouldIgnorePath(fullPath, entry.isDirectory())) {
|
|
509
558
|
continue;
|
|
510
559
|
}
|
|
511
560
|
|
|
@@ -516,7 +565,7 @@ Return as JSON: {"accessMethod": "...", "confidence": 0.0-1.0}`;
|
|
|
516
565
|
files.push(fullPath);
|
|
517
566
|
}
|
|
518
567
|
}
|
|
519
|
-
} catch
|
|
568
|
+
} catch {
|
|
520
569
|
// Ignore errors (permission denied, etc.)
|
|
521
570
|
}
|
|
522
571
|
|
|
@@ -535,6 +584,202 @@ Return as JSON: {"accessMethod": "...", "confidence": 0.0-1.0}`;
|
|
|
535
584
|
return this.options.ignorePatterns.some((pattern) => name.includes(pattern));
|
|
536
585
|
}
|
|
537
586
|
|
|
587
|
+
private async shouldIgnorePath(filePath: string, isDirectory = false): Promise<boolean> {
|
|
588
|
+
if (this.isDotDirectoryPath(filePath)) {
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const segments = normalize(filePath).split(sep);
|
|
593
|
+
if (segments.some((segment) => this.shouldIgnore(segment))) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return await this.isGitIgnored(filePath, isDirectory);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
private isDotDirectoryPath(filePath: string): boolean {
|
|
601
|
+
const segments = normalize(filePath).split(sep);
|
|
602
|
+
return segments.some((segment) => segment.startsWith('.') && segment.length > 1);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private isDotDirectoryName(name: string): boolean {
|
|
606
|
+
return name.startsWith('.') && name.length > 1;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private async isGitIgnored(filePath: string, isDirectory: boolean): Promise<boolean> {
|
|
610
|
+
if (!this.options.useGitExcludes) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
const matcher = await this.getIgnoreMatcher();
|
|
614
|
+
if (!matcher) {
|
|
615
|
+
return false;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const relativePath = this.getRepoRelativePath(filePath);
|
|
619
|
+
if (!relativePath) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const normalized = relativePath.split(sep).join('/');
|
|
624
|
+
const testPath = isDirectory ? `${normalized}/` : normalized;
|
|
625
|
+
return matcher.ignores(testPath);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
private getRepoRelativePath(filePath: string): string | null {
|
|
629
|
+
const repoPath = resolve(normalize(this.options.repoPath));
|
|
630
|
+
const normalizedPath = resolve(normalize(filePath));
|
|
631
|
+
|
|
632
|
+
if (normalizedPath === repoPath) {
|
|
633
|
+
return '';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (normalizedPath.startsWith(repoPath + sep)) {
|
|
637
|
+
return relative(repoPath, normalizedPath);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return normalize(filePath);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private async getIgnoreMatcher(): Promise<Ignore | null> {
|
|
644
|
+
if (this.ignoreMatcherLoaded) {
|
|
645
|
+
return this.ignoreMatcher;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
this.ignoreMatcherLoaded = true;
|
|
649
|
+
|
|
650
|
+
if (!this.options.useGitExcludes) {
|
|
651
|
+
this.ignoreMatcher = null;
|
|
652
|
+
return this.ignoreMatcher;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
const matcher = ignore();
|
|
657
|
+
const gitignoreFiles = await this.collectGitignoreFiles(this.options.repoPath);
|
|
658
|
+
const extraIgnoreFiles = this.collectExtraIgnoreFiles();
|
|
659
|
+
|
|
660
|
+
for (const filePath of gitignoreFiles) {
|
|
661
|
+
const content = await readFile(filePath, 'utf-8');
|
|
662
|
+
const rules = this.prefixGitignoreRules(filePath, content);
|
|
663
|
+
if (rules.length > 0) {
|
|
664
|
+
matcher.add(rules);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
for (const filePath of extraIgnoreFiles) {
|
|
669
|
+
try {
|
|
670
|
+
const content = await readFile(filePath, 'utf-8');
|
|
671
|
+
const rules = this.prefixGitignoreRules(filePath, content, '');
|
|
672
|
+
if (rules.length > 0) {
|
|
673
|
+
matcher.add(rules);
|
|
674
|
+
}
|
|
675
|
+
} catch {
|
|
676
|
+
// Ignore missing or unreadable global ignore files
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
this.ignoreMatcher = matcher;
|
|
681
|
+
} catch {
|
|
682
|
+
this.ignoreMatcher = null;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return this.ignoreMatcher;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private async collectGitignoreFiles(dir: string): Promise<string[]> {
|
|
689
|
+
const files: string[] = [];
|
|
690
|
+
|
|
691
|
+
try {
|
|
692
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
693
|
+
for (const entry of entries) {
|
|
694
|
+
if (entry.isDirectory()) {
|
|
695
|
+
if (this.isDotDirectoryName(entry.name) || this.shouldIgnore(entry.name)) {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
files.push(...(await this.collectGitignoreFiles(join(dir, entry.name))));
|
|
699
|
+
} else if (entry.isFile() && entry.name === '.gitignore') {
|
|
700
|
+
files.push(join(dir, entry.name));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
// Ignore errors
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return files;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private prefixGitignoreRules(filePath: string, content: string, basePrefix?: string): string[] {
|
|
711
|
+
const repoPath = resolve(normalize(this.options.repoPath));
|
|
712
|
+
const ignoreDir = dirname(filePath);
|
|
713
|
+
const relativeDir = relative(repoPath, ignoreDir).split(sep).join('/');
|
|
714
|
+
const prefix = basePrefix !== undefined ? basePrefix : relativeDir ? `${relativeDir}/` : '';
|
|
715
|
+
|
|
716
|
+
return content
|
|
717
|
+
.split('\n')
|
|
718
|
+
.map((line) => line.trim())
|
|
719
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
720
|
+
.map((line) => {
|
|
721
|
+
const negated = line.startsWith('!');
|
|
722
|
+
const raw = negated ? line.slice(1) : line;
|
|
723
|
+
const trimmed = raw.startsWith('/') ? raw.slice(1) : raw;
|
|
724
|
+
const scoped = `${prefix}${trimmed}`;
|
|
725
|
+
return negated ? `!${scoped}` : scoped;
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private collectExtraIgnoreFiles(): string[] {
|
|
730
|
+
const files = new Set<string>();
|
|
731
|
+
|
|
732
|
+
files.add(join(this.options.repoPath, '.git', 'info', 'exclude'));
|
|
733
|
+
|
|
734
|
+
const globalConfig = this.getGlobalExcludeFileFromGit();
|
|
735
|
+
if (globalConfig) {
|
|
736
|
+
files.add(globalConfig);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
for (const fallback of this.getDefaultGlobalExcludeFiles()) {
|
|
740
|
+
files.add(fallback);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return Array.from(files);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private getGlobalExcludeFileFromGit(): string | null {
|
|
747
|
+
try {
|
|
748
|
+
const output = execSync('git config --get core.excludesfile', {
|
|
749
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
750
|
+
})
|
|
751
|
+
.toString()
|
|
752
|
+
.trim();
|
|
753
|
+
|
|
754
|
+
if (!output) {
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return this.expandHomePath(output);
|
|
759
|
+
} catch {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private getDefaultGlobalExcludeFiles(): string[] {
|
|
765
|
+
const home = homedir();
|
|
766
|
+
return [
|
|
767
|
+
join(home, '.config', 'git', 'ignore'),
|
|
768
|
+
join(home, '.gitignore_global'),
|
|
769
|
+
join(home, '.gitignore')
|
|
770
|
+
];
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private expandHomePath(filePath: string): string {
|
|
774
|
+
if (filePath.startsWith('~/')) {
|
|
775
|
+
return join(homedir(), filePath.slice(2));
|
|
776
|
+
}
|
|
777
|
+
if (filePath === '~') {
|
|
778
|
+
return homedir();
|
|
779
|
+
}
|
|
780
|
+
return filePath;
|
|
781
|
+
}
|
|
782
|
+
|
|
538
783
|
/**
|
|
539
784
|
* Analyze only specific files for dependencies (for incremental updates)
|
|
540
785
|
* This is more efficient than full repository scan when only few files changed
|
|
@@ -555,6 +800,9 @@ Return as JSON: {"accessMethod": "...", "confidence": 0.0-1.0}`;
|
|
|
555
800
|
let totalLatencyMs = 0;
|
|
556
801
|
|
|
557
802
|
for (const filePath of filePaths) {
|
|
803
|
+
if (await this.shouldIgnorePath(filePath)) {
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
558
806
|
// Validate that the file path is safe before joining
|
|
559
807
|
const normalizedRepoPath = resolve(normalize(this.options.repoPath));
|
|
560
808
|
const normalizedFilePath = normalize(filePath);
|
package/src/llm/copilot.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Integrates with GitHub Copilot via CLI commands
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import type {
|
|
9
9
|
LLMProvider,
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
} from './client.js';
|
|
16
16
|
import { SYSTEM_PROMPT } from './prompts.js';
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
19
|
|
|
20
20
|
export class GitHubCopilotProvider implements LLMProvider {
|
|
21
21
|
private config: Required<LLMProviderConfig>;
|
|
@@ -44,14 +44,17 @@ export class GitHubCopilotProvider implements LLMProvider {
|
|
|
44
44
|
// Combine system prompt and user prompt for CLI
|
|
45
45
|
const fullPrompt = `${SYSTEM_PROMPT}\n\n${prompt}`;
|
|
46
46
|
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
// Use execFile to avoid shell escaping issues and command injection
|
|
48
|
+
// Pass prompt directly as an argument without manual escaping
|
|
49
|
+
const args = [
|
|
50
|
+
'copilot',
|
|
51
|
+
'-p',
|
|
52
|
+
fullPrompt,
|
|
53
|
+
'--silent',
|
|
54
|
+
'--allow-all-tools'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const { stdout, stderr } = await execFileAsync('gh', args, {
|
|
55
58
|
maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large responses
|
|
56
59
|
timeout: 60000 // 60 second timeout
|
|
57
60
|
});
|
package/src/llm/prompts.ts
CHANGED
|
@@ -17,11 +17,20 @@ INCLUDE these types of dependencies:
|
|
|
17
17
|
|
|
18
18
|
EXCLUDE these (handled by dependabot):
|
|
19
19
|
- NPM packages in package.json
|
|
20
|
-
- Python packages in requirements.txt
|
|
20
|
+
- Python packages in requirements.txt
|
|
21
21
|
- Rust crates in Cargo.toml
|
|
22
22
|
- Docker images in Dockerfile
|
|
23
23
|
- Any declared package manager dependencies
|
|
24
24
|
|
|
25
|
+
ALSO EXCLUDE (false positives):
|
|
26
|
+
- URLs that reference the repository itself (self-references)
|
|
27
|
+
- Relative file paths (e.g., CONTRIBUTING.md, docs/guide.md, ./src/utils)
|
|
28
|
+
- Placeholder URLs used in documentation examples (example.com, example.org, localhost)
|
|
29
|
+
- Internal documentation links within the same repository
|
|
30
|
+
- URLs with template variables or placeholders (e.g., issues/[NUMBER], user/repo)
|
|
31
|
+
|
|
32
|
+
Only return dependencies with confidence >= 0.7.
|
|
33
|
+
|
|
25
34
|
For each dependency found, provide:
|
|
26
35
|
1. url: The complete URL
|
|
27
36
|
2. name: A descriptive name
|
package/src/parsers/readme.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface ExtractedReference {
|
|
|
10
10
|
type: 'markdown-link' | 'bare-url' | 'reference-link';
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
// Patterns to skip (package managers, CI badges, shields.io)
|
|
13
|
+
// Patterns to skip (package managers, CI badges, shields.io, placeholders)
|
|
14
14
|
const SKIP_PATTERNS = [
|
|
15
15
|
/npmjs\.com\/package/,
|
|
16
16
|
/pypi\.org\/project/,
|
|
@@ -21,7 +21,13 @@ const SKIP_PATTERNS = [
|
|
|
21
21
|
/badge(s)?\..*\.svg/,
|
|
22
22
|
/travis-ci\.(org|com)/,
|
|
23
23
|
/circleci\.com/,
|
|
24
|
-
/github\.com\/.*\/actions
|
|
24
|
+
/github\.com\/.*\/actions/, // GitHub Actions badges
|
|
25
|
+
/example\.com/, // placeholder domain
|
|
26
|
+
/example\.org/, // placeholder domain
|
|
27
|
+
/localhost/, // local dev URLs
|
|
28
|
+
/127\.0\.0\.1/, // loopback address
|
|
29
|
+
/\[.*\]/, // template placeholders (e.g., issues/[NUMBER])
|
|
30
|
+
/github\.com\/user\/repo/ // common placeholder in docs
|
|
25
31
|
];
|
|
26
32
|
|
|
27
33
|
/**
|