@vibe-agent-toolkit/discovery 0.1.0-rc.7 → 0.1.0-rc.9

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.
@@ -0,0 +1,15 @@
1
+ import type { DetectedFormat } from '../types.js';
2
+ /**
3
+ * Detect file format based on filename
4
+ *
5
+ * Detection rules:
6
+ * - Exact match "SKILL.md" → claude-skill
7
+ * - Exact match "agent.yaml" or "agent.yml" → vat-agent
8
+ * - Extension ".md" → markdown
9
+ * - Everything else → unknown
10
+ *
11
+ * @param filePath - Path to file (can be relative or absolute)
12
+ * @returns Detected format
13
+ */
14
+ export declare function detectFormat(filePath: string): DetectedFormat;
15
+ //# sourceMappingURL=format-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-detector.d.ts","sourceRoot":"","sources":["../../src/detectors/format-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAkB7D"}
@@ -0,0 +1,29 @@
1
+ import * as path from 'node:path';
2
+ /**
3
+ * Detect file format based on filename
4
+ *
5
+ * Detection rules:
6
+ * - Exact match "SKILL.md" → claude-skill
7
+ * - Exact match "agent.yaml" or "agent.yml" → vat-agent
8
+ * - Extension ".md" → markdown
9
+ * - Everything else → unknown
10
+ *
11
+ * @param filePath - Path to file (can be relative or absolute)
12
+ * @returns Detected format
13
+ */
14
+ export function detectFormat(filePath) {
15
+ const basename = path.basename(filePath);
16
+ // Exact matches (case-sensitive)
17
+ if (basename === 'SKILL.md') {
18
+ return 'claude-skill';
19
+ }
20
+ if (basename === 'agent.yaml' || basename === 'agent.yml') {
21
+ return 'vat-agent';
22
+ }
23
+ // Extension-based detection (case-insensitive)
24
+ if (basename.toLowerCase().endsWith('.md')) {
25
+ return 'markdown';
26
+ }
27
+ return 'unknown';
28
+ }
29
+ //# sourceMappingURL=format-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-detector.js","sourceRoot":"","sources":["../../src/detectors/format-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAIlC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEzC,iCAAiC;IACjC,IAAI,QAAQ,KAAK,UAAU,EAAE,CAAC;QAC5B,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,IAAI,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;QAC1D,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,+CAA+C;IAC/C,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3C,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface PatternFilterOptions {
2
+ /** Include patterns (if specified, only matching files included) */
3
+ include?: string[];
4
+ /** Exclude patterns (applied after include) */
5
+ exclude?: string[];
6
+ }
7
+ /**
8
+ * Create a filter function for include/exclude patterns
9
+ *
10
+ * Uses picomatch for fast glob matching. Exclude patterns are applied after include.
11
+ *
12
+ * @param options - Pattern filter options
13
+ * @returns Filter function (returns true if file should be included)
14
+ */
15
+ export declare function createPatternFilter(options: PatternFilterOptions): (filePath: string) => boolean;
16
+ //# sourceMappingURL=pattern-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-filter.d.ts","sourceRoot":"","sources":["../../src/filters/pattern-filter.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CA0B/B"}
@@ -0,0 +1,32 @@
1
+ import picomatch from 'picomatch';
2
+ /**
3
+ * Create a filter function for include/exclude patterns
4
+ *
5
+ * Uses picomatch for fast glob matching. Exclude patterns are applied after include.
6
+ *
7
+ * @param options - Pattern filter options
8
+ * @returns Filter function (returns true if file should be included)
9
+ */
10
+ export function createPatternFilter(options) {
11
+ const { include, exclude } = options;
12
+ // Compile patterns once for performance
13
+ // Use { contains: true } to match paths containing patterns (e.g., 'node_modules' matches 'path/node_modules/file')
14
+ const includeMatcher = include?.length
15
+ ? picomatch(include, { contains: true })
16
+ : null;
17
+ const excludeMatcher = exclude?.length
18
+ ? picomatch(exclude, { contains: true })
19
+ : null;
20
+ return (filePath) => {
21
+ // If include patterns specified, file must match at least one
22
+ if (includeMatcher && !includeMatcher(filePath)) {
23
+ return false;
24
+ }
25
+ // If exclude patterns specified, file must not match any
26
+ if (excludeMatcher?.(filePath)) {
27
+ return false;
28
+ }
29
+ return true;
30
+ };
31
+ }
32
+ //# sourceMappingURL=pattern-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pattern-filter.js","sourceRoot":"","sources":["../../src/filters/pattern-filter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAUlC;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA6B;IAE7B,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAErC,wCAAwC;IACxC,oHAAoH;IACpH,MAAM,cAAc,GAAG,OAAO,EAAE,MAAM;QACpC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,cAAc,GAAG,OAAO,EAAE,MAAM;QACpC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,CAAC,QAAgB,EAAW,EAAE;QACnC,8DAA8D;QAC9D,IAAI,cAAc,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,yDAAyD;QACzD,IAAI,cAAc,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export { detectFormat } from './detectors/format-detector.js';
3
+ export { createPatternFilter } from './filters/pattern-filter.js';
4
+ export { scan } from './scanners/local-scanner.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC"}
@@ -2,3 +2,4 @@ export * from './types.js';
2
2
  export { detectFormat } from './detectors/format-detector.js';
3
3
  export { createPatternFilter } from './filters/pattern-filter.js';
4
4
  export { scan } from './scanners/local-scanner.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ScanOptions, ScanSummary } from '../types.js';
2
+ /**
3
+ * Scan local filesystem for VAT agents and Claude Skills
4
+ *
5
+ * @param options - Scan options
6
+ * @returns Scan summary with all discovered files
7
+ */
8
+ export declare function scan(options: ScanOptions): Promise<ScanSummary>;
9
+ //# sourceMappingURL=local-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-scanner.d.ts","sourceRoot":"","sources":["../../src/scanners/local-scanner.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAc,WAAW,EAAE,MAAM,aAAa,CAAC;AAExE;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAsFrE"}
@@ -0,0 +1,87 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { crawlDirectory, isGitIgnored } from '@vibe-agent-toolkit/utils';
4
+ import { detectFormat } from '../detectors/format-detector.js';
5
+ import { createPatternFilter } from '../filters/pattern-filter.js';
6
+ /**
7
+ * Scan local filesystem for VAT agents and Claude Skills
8
+ *
9
+ * @param options - Scan options
10
+ * @returns Scan summary with all discovered files
11
+ */
12
+ export async function scan(options) {
13
+ const { path: targetPath, recursive = false, include, exclude } = options;
14
+ // Resolve to absolute path
15
+ const absolutePath = path.resolve(targetPath);
16
+ // Check if target exists
17
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath is validated user input
18
+ if (!fs.existsSync(absolutePath)) {
19
+ throw new Error(`Path does not exist: ${absolutePath}`);
20
+ }
21
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
22
+ const stat = fs.statSync(absolutePath);
23
+ // Determine scan root for relative paths
24
+ const scanRoot = stat.isDirectory() ? absolutePath : path.dirname(absolutePath);
25
+ // Get file list
26
+ let filePaths;
27
+ if (stat.isFile()) {
28
+ filePaths = [absolutePath];
29
+ }
30
+ else if (stat.isDirectory()) {
31
+ if (recursive) {
32
+ filePaths = await crawlDirectory({
33
+ baseDir: absolutePath,
34
+ respectGitignore: false, // We handle gitignore separately
35
+ exclude: [], // Don't exclude anything - we handle filtering ourselves
36
+ });
37
+ }
38
+ else {
39
+ // Non-recursive: only immediate children
40
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
41
+ filePaths = fs.readdirSync(absolutePath)
42
+ .map(name => path.join(absolutePath, name))
43
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- paths are from validated directory
44
+ .filter(p => fs.statSync(p).isFile());
45
+ }
46
+ }
47
+ else {
48
+ throw new Error(`Path is neither file nor directory: ${absolutePath}`);
49
+ }
50
+ // Create pattern filter
51
+ const patternFilter = createPatternFilter({
52
+ ...(include && { include }),
53
+ ...(exclude && { exclude }),
54
+ });
55
+ // Process each file
56
+ const results = [];
57
+ for (const filePath of filePaths) {
58
+ const relativePath = path.relative(scanRoot, filePath);
59
+ // Apply pattern filter
60
+ if (!patternFilter(relativePath)) {
61
+ continue;
62
+ }
63
+ const format = detectFormat(filePath);
64
+ const gitIgnored = isGitIgnored(filePath, scanRoot);
65
+ results.push({
66
+ path: filePath,
67
+ format,
68
+ isGitIgnored: gitIgnored,
69
+ relativePath,
70
+ });
71
+ }
72
+ // Build summary
73
+ const byFormat = results.reduce((acc, r) => {
74
+ acc[r.format] = (acc[r.format] ?? 0) + 1;
75
+ return acc;
76
+ }, {});
77
+ const sourceFiles = results.filter(r => !r.isGitIgnored);
78
+ const buildOutputs = results.filter(r => r.isGitIgnored);
79
+ return {
80
+ results,
81
+ totalScanned: results.length,
82
+ byFormat: byFormat,
83
+ sourceFiles,
84
+ buildOutputs,
85
+ };
86
+ }
87
+ //# sourceMappingURL=local-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-scanner.js","sourceRoot":"","sources":["../../src/scanners/local-scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Format types that discovery can detect
3
+ */
4
+ export type DetectedFormat = 'claude-skill' | 'vat-agent' | 'markdown' | 'unknown';
5
+ /**
6
+ * Options for scanning/discovery operations
7
+ */
8
+ export interface ScanOptions {
9
+ /** Path to scan (file or directory) */
10
+ path: string;
11
+ /** Recursive scan (search subdirectories) */
12
+ recursive?: boolean;
13
+ /** Include patterns (glob) */
14
+ include?: string[];
15
+ /** Exclude patterns (glob) */
16
+ exclude?: string[];
17
+ /** Follow symbolic links */
18
+ followSymlinks?: boolean;
19
+ }
20
+ /**
21
+ * Result of scanning a single file
22
+ */
23
+ export interface ScanResult {
24
+ /** Absolute path to file */
25
+ path: string;
26
+ /** Detected format */
27
+ format: DetectedFormat;
28
+ /** Is this file gitignored (likely build output) */
29
+ isGitIgnored: boolean;
30
+ /** Relative path from scan root */
31
+ relativePath: string;
32
+ }
33
+ /**
34
+ * Summary of scan operation
35
+ */
36
+ export interface ScanSummary {
37
+ /** All discovered files */
38
+ results: ScanResult[];
39
+ /** Total files scanned */
40
+ totalScanned: number;
41
+ /** Files by format */
42
+ byFormat: Record<DetectedFormat, number>;
43
+ /** Source files (not gitignored) */
44
+ sourceFiles: ScanResult[];
45
+ /** Build outputs (gitignored) */
46
+ buildOutputs: ScanResult[];
47
+ }
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB,cAAc,GACd,WAAW,GACX,UAAU,GACV,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;IAEb,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB,4BAA4B;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAC;IAEb,sBAAsB;IACtB,MAAM,EAAE,cAAc,CAAC;IAEvB,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IAEtB,mCAAmC;IACnC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,OAAO,EAAE,UAAU,EAAE,CAAC;IAEtB,0BAA0B;IAC1B,YAAY,EAAE,MAAM,CAAC;IAErB,sBAAsB;IACtB,QAAQ,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;IAEzC,oCAAoC;IACpC,WAAW,EAAE,UAAU,EAAE,CAAC;IAE1B,iCAAiC;IACjC,YAAY,EAAE,UAAU,EAAE,CAAC;CAC5B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-agent-toolkit/discovery",
3
- "version": "0.1.0-rc.7",
3
+ "version": "0.1.0-rc.9",
4
4
  "description": "Intelligent file discovery for VAT agents and Claude Skills",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,6 +11,10 @@
11
11
  "default": "./dist/index.js"
12
12
  }
13
13
  },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
14
18
  "scripts": {
15
19
  "build": "tsc",
16
20
  "test": "vitest run",
@@ -18,7 +22,7 @@
18
22
  "typecheck": "tsc --noEmit"
19
23
  },
20
24
  "dependencies": {
21
- "@vibe-agent-toolkit/utils": "workspace:*",
25
+ "@vibe-agent-toolkit/utils": "0.1.0-rc.9",
22
26
  "picomatch": "^4.0.2"
23
27
  },
24
28
  "devDependencies": {
@@ -1,35 +0,0 @@
1
- import * as path from 'node:path';
2
-
3
- import type { DetectedFormat } from '../types.js';
4
-
5
- /**
6
- * Detect file format based on filename
7
- *
8
- * Detection rules:
9
- * - Exact match "SKILL.md" → claude-skill
10
- * - Exact match "agent.yaml" or "agent.yml" → vat-agent
11
- * - Extension ".md" → markdown
12
- * - Everything else → unknown
13
- *
14
- * @param filePath - Path to file (can be relative or absolute)
15
- * @returns Detected format
16
- */
17
- export function detectFormat(filePath: string): DetectedFormat {
18
- const basename = path.basename(filePath);
19
-
20
- // Exact matches (case-sensitive)
21
- if (basename === 'SKILL.md') {
22
- return 'claude-skill';
23
- }
24
-
25
- if (basename === 'agent.yaml' || basename === 'agent.yml') {
26
- return 'vat-agent';
27
- }
28
-
29
- // Extension-based detection (case-insensitive)
30
- if (basename.toLowerCase().endsWith('.md')) {
31
- return 'markdown';
32
- }
33
-
34
- return 'unknown';
35
- }
@@ -1,47 +0,0 @@
1
- import picomatch from 'picomatch';
2
-
3
- export interface PatternFilterOptions {
4
- /** Include patterns (if specified, only matching files included) */
5
- include?: string[];
6
-
7
- /** Exclude patterns (applied after include) */
8
- exclude?: string[];
9
- }
10
-
11
- /**
12
- * Create a filter function for include/exclude patterns
13
- *
14
- * Uses picomatch for fast glob matching. Exclude patterns are applied after include.
15
- *
16
- * @param options - Pattern filter options
17
- * @returns Filter function (returns true if file should be included)
18
- */
19
- export function createPatternFilter(
20
- options: PatternFilterOptions
21
- ): (filePath: string) => boolean {
22
- const { include, exclude } = options;
23
-
24
- // Compile patterns once for performance
25
- // Use { contains: true } to match paths containing patterns (e.g., 'node_modules' matches 'path/node_modules/file')
26
- const includeMatcher = include?.length
27
- ? picomatch(include, { contains: true })
28
- : null;
29
-
30
- const excludeMatcher = exclude?.length
31
- ? picomatch(exclude, { contains: true })
32
- : null;
33
-
34
- return (filePath: string): boolean => {
35
- // If include patterns specified, file must match at least one
36
- if (includeMatcher && !includeMatcher(filePath)) {
37
- return false;
38
- }
39
-
40
- // If exclude patterns specified, file must not match any
41
- if (excludeMatcher?.(filePath)) {
42
- return false;
43
- }
44
-
45
- return true;
46
- };
47
- }
@@ -1,102 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import * as path from 'node:path';
3
-
4
- import { crawlDirectory , isGitIgnored } from '@vibe-agent-toolkit/utils';
5
-
6
- import { detectFormat } from '../detectors/format-detector.js';
7
- import { createPatternFilter } from '../filters/pattern-filter.js';
8
- import type { ScanOptions, ScanResult, ScanSummary } from '../types.js';
9
-
10
- /**
11
- * Scan local filesystem for VAT agents and Claude Skills
12
- *
13
- * @param options - Scan options
14
- * @returns Scan summary with all discovered files
15
- */
16
- export async function scan(options: ScanOptions): Promise<ScanSummary> {
17
- const { path: targetPath, recursive = false, include, exclude } = options;
18
-
19
- // Resolve to absolute path
20
- const absolutePath = path.resolve(targetPath);
21
-
22
- // Check if target exists
23
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath is validated user input
24
- if (!fs.existsSync(absolutePath)) {
25
- throw new Error(`Path does not exist: ${absolutePath}`);
26
- }
27
-
28
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
29
- const stat = fs.statSync(absolutePath);
30
-
31
- // Determine scan root for relative paths
32
- const scanRoot = stat.isDirectory() ? absolutePath : path.dirname(absolutePath);
33
-
34
- // Get file list
35
- let filePaths: string[];
36
-
37
- if (stat.isFile()) {
38
- filePaths = [absolutePath];
39
- } else if (stat.isDirectory()) {
40
- if (recursive) {
41
- filePaths = await crawlDirectory({
42
- baseDir: absolutePath,
43
- respectGitignore: false, // We handle gitignore separately
44
- exclude: [], // Don't exclude anything - we handle filtering ourselves
45
- });
46
- } else {
47
- // Non-recursive: only immediate children
48
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- absolutePath validated above
49
- filePaths = fs.readdirSync(absolutePath)
50
- .map(name => path.join(absolutePath, name))
51
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- paths are from validated directory
52
- .filter(p => fs.statSync(p).isFile());
53
- }
54
- } else {
55
- throw new Error(`Path is neither file nor directory: ${absolutePath}`);
56
- }
57
-
58
- // Create pattern filter
59
- const patternFilter = createPatternFilter({
60
- ...(include && { include }),
61
- ...(exclude && { exclude }),
62
- });
63
-
64
- // Process each file
65
- const results: ScanResult[] = [];
66
-
67
- for (const filePath of filePaths) {
68
- const relativePath = path.relative(scanRoot, filePath);
69
-
70
- // Apply pattern filter
71
- if (!patternFilter(relativePath)) {
72
- continue;
73
- }
74
-
75
- const format = detectFormat(filePath);
76
- const gitIgnored = isGitIgnored(filePath, scanRoot);
77
-
78
- results.push({
79
- path: filePath,
80
- format,
81
- isGitIgnored: gitIgnored,
82
- relativePath,
83
- });
84
- }
85
-
86
- // Build summary
87
- const byFormat = results.reduce((acc, r) => {
88
- acc[r.format] = (acc[r.format] ?? 0) + 1;
89
- return acc;
90
- }, {} as Record<string, number>);
91
-
92
- const sourceFiles = results.filter(r => !r.isGitIgnored);
93
- const buildOutputs = results.filter(r => r.isGitIgnored);
94
-
95
- return {
96
- results,
97
- totalScanned: results.length,
98
- byFormat: byFormat as ScanSummary['byFormat'],
99
- sourceFiles,
100
- buildOutputs,
101
- };
102
- }
package/src/types.ts DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Format types that discovery can detect
3
- */
4
- export type DetectedFormat =
5
- | 'claude-skill' // SKILL.md
6
- | 'vat-agent' // agent.yaml
7
- | 'markdown' // *.md (resource file)
8
- | 'unknown'; // Other files
9
-
10
- /**
11
- * Options for scanning/discovery operations
12
- */
13
- export interface ScanOptions {
14
- /** Path to scan (file or directory) */
15
- path: string;
16
-
17
- /** Recursive scan (search subdirectories) */
18
- recursive?: boolean;
19
-
20
- /** Include patterns (glob) */
21
- include?: string[];
22
-
23
- /** Exclude patterns (glob) */
24
- exclude?: string[];
25
-
26
- /** Follow symbolic links */
27
- followSymlinks?: boolean;
28
- }
29
-
30
- /**
31
- * Result of scanning a single file
32
- */
33
- export interface ScanResult {
34
- /** Absolute path to file */
35
- path: string;
36
-
37
- /** Detected format */
38
- format: DetectedFormat;
39
-
40
- /** Is this file gitignored (likely build output) */
41
- isGitIgnored: boolean;
42
-
43
- /** Relative path from scan root */
44
- relativePath: string;
45
- }
46
-
47
- /**
48
- * Summary of scan operation
49
- */
50
- export interface ScanSummary {
51
- /** All discovered files */
52
- results: ScanResult[];
53
-
54
- /** Total files scanned */
55
- totalScanned: number;
56
-
57
- /** Files by format */
58
- byFormat: Record<DetectedFormat, number>;
59
-
60
- /** Source files (not gitignored) */
61
- sourceFiles: ScanResult[];
62
-
63
- /** Build outputs (gitignored) */
64
- buildOutputs: ScanResult[];
65
- }
@@ -1,45 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- import { detectFormat } from '../src/detectors/format-detector.js';
4
-
5
- const CLAUDE_SKILL_FORMAT = 'claude-skill';
6
-
7
- describe('detectFormat', () => {
8
- it('should detect SKILL.md as claude-skill', () => {
9
- expect(detectFormat('SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
10
- expect(detectFormat('/path/to/SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
11
- expect(detectFormat('./my-skill/SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
12
- });
13
-
14
- it('should detect agent.yaml as vat-agent', () => {
15
- expect(detectFormat('agent.yaml')).toBe('vat-agent');
16
- expect(detectFormat('/path/to/agent.yaml')).toBe('vat-agent');
17
- });
18
-
19
- it('should detect agent.yml as vat-agent', () => {
20
- expect(detectFormat('agent.yml')).toBe('vat-agent');
21
- });
22
-
23
- it('should detect .md files as markdown', () => {
24
- expect(detectFormat('README.md')).toBe('markdown');
25
- expect(detectFormat('docs/guide.md')).toBe('markdown');
26
- expect(detectFormat('reference/api.md')).toBe('markdown');
27
- });
28
-
29
- it('should not detect SKILL.md as markdown', () => {
30
- expect(detectFormat('SKILL.md')).not.toBe('markdown');
31
- expect(detectFormat('SKILL.md')).toBe(CLAUDE_SKILL_FORMAT);
32
- });
33
-
34
- it('should return unknown for other files', () => {
35
- expect(detectFormat('package.json')).toBe('unknown');
36
- expect(detectFormat('index.ts')).toBe('unknown');
37
- expect(detectFormat('test.txt')).toBe('unknown');
38
- });
39
-
40
- it('should be case-sensitive for SKILL.md', () => {
41
- expect(detectFormat('skill.md')).toBe('markdown');
42
- expect(detectFormat('Skill.md')).toBe('markdown');
43
- expect(detectFormat('SKILL.MD')).toBe('markdown');
44
- });
45
- });
@@ -1,109 +0,0 @@
1
- /* eslint-disable security/detect-non-literal-fs-filename -- test file uses controlled temp directory */
2
- import { spawnSync } from 'node:child_process';
3
- import * as fs from 'node:fs';
4
- import * as os from 'node:os';
5
- import * as path from 'node:path';
6
-
7
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
-
9
- import { scan } from '../src/scanners/local-scanner.js';
10
-
11
- describe('scan', () => {
12
- let tempDir: string;
13
-
14
- beforeEach(() => {
15
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'discovery-test-'));
16
- });
17
-
18
- afterEach(() => {
19
- fs.rmSync(tempDir, { recursive: true, force: true });
20
- });
21
-
22
- it('should scan single SKILL.md file', async () => {
23
- const skillPath = path.join(tempDir, 'SKILL.md');
24
- fs.writeFileSync(skillPath, '# Test Skill');
25
-
26
- const result = await scan({ path: skillPath });
27
-
28
- expect(result.totalScanned).toBe(1);
29
- expect(result.results).toHaveLength(1);
30
- expect(result.results[0]?.format).toBe('claude-skill');
31
- expect(result.results[0]?.path).toBe(skillPath);
32
- });
33
-
34
- it('should scan directory non-recursively', async () => {
35
- fs.writeFileSync(path.join(tempDir, 'SKILL.md'), '# Skill');
36
- fs.writeFileSync(path.join(tempDir, 'README.md'), '# Readme');
37
- fs.mkdirSync(path.join(tempDir, 'sub'));
38
- fs.writeFileSync(path.join(tempDir, 'sub', 'agent.yaml'), 'name: test');
39
-
40
- const result = await scan({ path: tempDir, recursive: false });
41
-
42
- expect(result.totalScanned).toBe(2);
43
- expect(result.byFormat['claude-skill']).toBe(1);
44
- expect(result.byFormat['markdown']).toBe(1);
45
- });
46
-
47
- it('should scan directory recursively', async () => {
48
- fs.writeFileSync(path.join(tempDir, 'SKILL.md'), '# Skill');
49
- fs.mkdirSync(path.join(tempDir, 'sub'));
50
- fs.writeFileSync(path.join(tempDir, 'sub', 'agent.yaml'), 'name: test');
51
-
52
- const result = await scan({ path: tempDir, recursive: true });
53
-
54
- expect(result.totalScanned).toBe(2);
55
- expect(result.byFormat['claude-skill']).toBe(1);
56
- expect(result.byFormat['vat-agent']).toBe(1);
57
- });
58
-
59
- it('should respect include patterns', async () => {
60
- fs.writeFileSync(path.join(tempDir, 'test.md'), '# Test');
61
- fs.writeFileSync(path.join(tempDir, 'test.ts'), 'code');
62
-
63
- const result = await scan({
64
- path: tempDir,
65
- include: ['*.md']
66
- });
67
-
68
- expect(result.totalScanned).toBe(1);
69
- expect(result.results[0]?.format).toBe('markdown');
70
- });
71
-
72
- it('should respect exclude patterns', async () => {
73
- fs.mkdirSync(path.join(tempDir, 'node_modules'));
74
- fs.writeFileSync(path.join(tempDir, 'README.md'), '# Readme');
75
- fs.writeFileSync(path.join(tempDir, 'node_modules', 'pkg.md'), '# Pkg');
76
-
77
- const result = await scan({
78
- path: tempDir,
79
- recursive: true,
80
- exclude: ['**/node_modules/**']
81
- });
82
-
83
- expect(result.totalScanned).toBe(1);
84
- expect(result.results[0]?.relativePath).toBe('README.md');
85
- });
86
-
87
- it('should detect gitignored files', async () => {
88
- // Initialize git repo for git check-ignore to work
89
- const gitPath = 'git';
90
- // eslint-disable-next-line sonarjs/no-os-command-from-path -- test setup uses git from PATH
91
- spawnSync(gitPath, ['init'], { cwd: tempDir, stdio: 'pipe' });
92
- // eslint-disable-next-line sonarjs/no-os-command-from-path -- test setup uses git from PATH
93
- spawnSync(gitPath, ['config', 'user.email', 'test@example.com'], { cwd: tempDir, stdio: 'pipe' });
94
- // eslint-disable-next-line sonarjs/no-os-command-from-path -- test setup uses git from PATH
95
- spawnSync(gitPath, ['config', 'user.name', 'Test User'], { cwd: tempDir, stdio: 'pipe' });
96
-
97
- fs.writeFileSync(path.join(tempDir, '.gitignore'), 'dist/\n');
98
- fs.mkdirSync(path.join(tempDir, 'dist'));
99
- fs.writeFileSync(path.join(tempDir, 'dist', 'SKILL.md'), '# Built');
100
- fs.writeFileSync(path.join(tempDir, 'SKILL.md'), '# Source');
101
-
102
- const result = await scan({ path: tempDir, recursive: true });
103
-
104
- expect(result.totalScanned).toBe(2);
105
- expect(result.sourceFiles).toHaveLength(1);
106
- expect(result.buildOutputs).toHaveLength(1);
107
- expect(result.sourceFiles[0]?.relativePath).toBe('SKILL.md');
108
- });
109
- });
@@ -1,74 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
-
3
- import { createPatternFilter } from '../src/filters/pattern-filter.js';
4
-
5
- describe('createPatternFilter', () => {
6
- describe('include patterns', () => {
7
- it('should include files matching pattern', () => {
8
- const filter = createPatternFilter({ include: ['*.md'] });
9
-
10
- expect(filter('test.md')).toBe(true);
11
- expect(filter('docs/guide.md')).toBe(true);
12
- expect(filter('test.ts')).toBe(false);
13
- });
14
-
15
- it('should support glob patterns', () => {
16
- const filter = createPatternFilter({ include: ['**/*.test.ts'] });
17
-
18
- expect(filter('src/utils.test.ts')).toBe(true);
19
- expect(filter('test/unit/parser.test.ts')).toBe(true);
20
- expect(filter('src/utils.ts')).toBe(false);
21
- });
22
-
23
- it('should support multiple include patterns', () => {
24
- const filter = createPatternFilter({
25
- include: ['*.md', '*.yaml']
26
- });
27
-
28
- expect(filter('README.md')).toBe(true);
29
- expect(filter('config.yaml')).toBe(true);
30
- expect(filter('index.ts')).toBe(false);
31
- });
32
- });
33
-
34
- describe('exclude patterns', () => {
35
- it('should exclude files matching pattern', () => {
36
- const filter = createPatternFilter({ exclude: ['node_modules'] });
37
-
38
- expect(filter('node_modules/pkg/index.js')).toBe(false);
39
- expect(filter('src/index.ts')).toBe(true);
40
- });
41
-
42
- it('should support glob patterns', () => {
43
- const filter = createPatternFilter({
44
- exclude: ['**/dist/**', '**/*.test.ts']
45
- });
46
-
47
- expect(filter('packages/cli/dist/index.js')).toBe(false);
48
- expect(filter('src/utils.test.ts')).toBe(false);
49
- expect(filter('src/utils.ts')).toBe(true);
50
- });
51
- });
52
-
53
- describe('include + exclude patterns', () => {
54
- it('should apply exclude after include', () => {
55
- const filter = createPatternFilter({
56
- include: ['**/*.md'],
57
- exclude: ['**/node_modules/**'],
58
- });
59
-
60
- expect(filter('README.md')).toBe(true);
61
- expect(filter('docs/guide.md')).toBe(true);
62
- expect(filter('node_modules/pkg/README.md')).toBe(false);
63
- });
64
- });
65
-
66
- describe('no patterns', () => {
67
- it('should include all files when no patterns specified', () => {
68
- const filter = createPatternFilter({});
69
-
70
- expect(filter('any-file.txt')).toBe(true);
71
- expect(filter('path/to/file.js')).toBe(true);
72
- });
73
- });
74
- });
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "composite": true,
5
- "outDir": "./dist",
6
- "rootDir": "./src"
7
- },
8
- "include": ["src/**/*"],
9
- "references": [
10
- { "path": "../utils" }
11
- ]
12
- }