@vibe-agent-toolkit/utils 0.1.0-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 ADDED
@@ -0,0 +1,34 @@
1
+ # @vibe-agent-toolkit/utils
2
+
3
+ Core shared utilities with no dependencies on other packages.
4
+
5
+ ## Philosophy
6
+
7
+ This package provides utilities that are needed by multiple packages in the toolkit. Utilities are **added as real needs arise**, not speculatively.
8
+
9
+ If you need a utility function and multiple packages would benefit from it, add it here. Otherwise, keep it local to the package that needs it.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ bun add @vibe-agent-toolkit/utils
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```typescript
20
+ import { /* utilities will be added here */ } from '@vibe-agent-toolkit/utils';
21
+ ```
22
+
23
+ ## Current Utilities
24
+
25
+ Currently minimal - utilities will be added as needed by other packages.
26
+
27
+ Future additions may include:
28
+ - Schema validation utilities (Zod helpers, JSON Schema conversion)
29
+ - Cross-platform helpers (process spawning, file operations)
30
+ - Common type guards and assertions
31
+
32
+ ## License
33
+
34
+ MIT
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Options for directory crawling
3
+ */
4
+ export interface CrawlOptions {
5
+ /** Base directory to start crawl */
6
+ baseDir: string;
7
+ /** Include patterns (glob) - default: ['**\/*'] */
8
+ include?: string[];
9
+ /** Exclude patterns (glob) - default: ['**\/node_modules/**', '**\/.git/**'] */
10
+ exclude?: string[];
11
+ /** Follow symbolic links (default: false) */
12
+ followSymlinks?: boolean;
13
+ /** Return absolute paths in results (default: true) */
14
+ absolute?: boolean;
15
+ /** Only return files (not directories) - default: true */
16
+ filesOnly?: boolean;
17
+ /** Respect .gitignore files (default: true) */
18
+ respectGitignore?: boolean;
19
+ }
20
+ /**
21
+ * Crawl a directory tree and return matching files (async)
22
+ *
23
+ * Uses picomatch for glob pattern matching (same as Vitest)
24
+ * Cross-platform compatible
25
+ *
26
+ * @param options - Crawl options
27
+ * @returns Promise resolving to array of matching file paths
28
+ *
29
+ * @example
30
+ * const files = await crawlDirectory({
31
+ * baseDir: '/project',
32
+ * include: ['**\/*.md'],
33
+ * exclude: ['**\/node_modules/**'],
34
+ * });
35
+ */
36
+ export declare function crawlDirectory(options: CrawlOptions): Promise<string[]>;
37
+ /**
38
+ * Crawl a directory tree and return matching files (synchronous)
39
+ *
40
+ * Uses picomatch for glob pattern matching (same as Vitest)
41
+ * Cross-platform compatible
42
+ *
43
+ * @param options - Crawl options
44
+ * @returns Array of matching file paths
45
+ *
46
+ * @example
47
+ * const files = crawlDirectorySync({
48
+ * baseDir: '/project',
49
+ * include: ['**\/*.md'],
50
+ * exclude: ['**\/node_modules/**'],
51
+ * });
52
+ */
53
+ export declare function crawlDirectorySync(options: CrawlOptions): string[];
54
+ //# sourceMappingURL=file-crawler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-crawler.d.ts","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CAgKlE"}
@@ -0,0 +1,177 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import picomatch from 'picomatch';
4
+ import { findGitRoot, loadGitignoreRules } from './gitignore-checker.js';
5
+ /**
6
+ * Default exclude patterns that are almost always unwanted
7
+ */
8
+ const DEFAULT_EXCLUDE = ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/coverage/**'];
9
+ /**
10
+ * Crawl a directory tree and return matching files (async)
11
+ *
12
+ * Uses picomatch for glob pattern matching (same as Vitest)
13
+ * Cross-platform compatible
14
+ *
15
+ * @param options - Crawl options
16
+ * @returns Promise resolving to array of matching file paths
17
+ *
18
+ * @example
19
+ * const files = await crawlDirectory({
20
+ * baseDir: '/project',
21
+ * include: ['**\/*.md'],
22
+ * exclude: ['**\/node_modules/**'],
23
+ * });
24
+ */
25
+ export async function crawlDirectory(options) {
26
+ return crawlDirectorySync(options);
27
+ }
28
+ /**
29
+ * Crawl a directory tree and return matching files (synchronous)
30
+ *
31
+ * Uses picomatch for glob pattern matching (same as Vitest)
32
+ * Cross-platform compatible
33
+ *
34
+ * @param options - Crawl options
35
+ * @returns Array of matching file paths
36
+ *
37
+ * @example
38
+ * const files = crawlDirectorySync({
39
+ * baseDir: '/project',
40
+ * include: ['**\/*.md'],
41
+ * exclude: ['**\/node_modules/**'],
42
+ * });
43
+ */
44
+ export function crawlDirectorySync(options) {
45
+ const { baseDir, include = ['**/*'], exclude = DEFAULT_EXCLUDE, followSymlinks = false, absolute = true, filesOnly = true, respectGitignore = true, } = options;
46
+ // Resolve base directory to absolute path
47
+ const resolvedBaseDir = path.resolve(baseDir);
48
+ // Ensure base directory exists
49
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- baseDir is from controlled config, not user input
50
+ if (!fs.existsSync(resolvedBaseDir)) {
51
+ throw new Error(`Base directory does not exist: ${resolvedBaseDir}`);
52
+ }
53
+ // Ensure base directory is actually a directory
54
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- resolved path validated above
55
+ const baseStat = fs.statSync(resolvedBaseDir);
56
+ if (!baseStat.isDirectory()) {
57
+ throw new Error(`Base path is not a directory: ${resolvedBaseDir}`);
58
+ }
59
+ // Load gitignore rules if requested
60
+ let gitignoreChecker = null;
61
+ let gitRoot = null;
62
+ if (respectGitignore) {
63
+ gitRoot = findGitRoot(resolvedBaseDir);
64
+ if (gitRoot) {
65
+ gitignoreChecker = loadGitignoreRules(gitRoot, resolvedBaseDir);
66
+ }
67
+ }
68
+ // Compile glob patterns using picomatch
69
+ const isIncluded = picomatch(include);
70
+ const isExcluded = exclude.length > 0 ? picomatch(exclude) : () => false;
71
+ const results = [];
72
+ /**
73
+ * Check if a path should be excluded based on patterns and gitignore
74
+ */
75
+ function shouldExclude(normalizedPath, fullPath) {
76
+ // Check explicit exclude patterns
77
+ if (isExcluded(normalizedPath) || isExcluded(normalizedPath + '/')) {
78
+ return true;
79
+ }
80
+ // Check gitignore rules if enabled
81
+ if (gitignoreChecker && gitRoot) {
82
+ // Get path relative to git root for gitignore checking
83
+ const relativeToGitRoot = path.relative(gitRoot, fullPath);
84
+ const normalizedGitPath = relativeToGitRoot.split(path.sep).join('/');
85
+ if (gitignoreChecker.ignores(normalizedGitPath)) {
86
+ return true;
87
+ }
88
+ }
89
+ return false;
90
+ }
91
+ /**
92
+ * Add a path to results if it matches include patterns
93
+ */
94
+ function addToResults(normalizedPath, fullPath, relativePath) {
95
+ if (isIncluded(normalizedPath)) {
96
+ results.push(absolute ? fullPath : relativePath);
97
+ }
98
+ }
99
+ /**
100
+ * Process a symbolic link entry
101
+ */
102
+ function processSymlink(fullPath, normalizedPath, relativePath) {
103
+ if (!followSymlinks) {
104
+ return;
105
+ }
106
+ // Resolve symlink and check if it's a directory or file
107
+ let targetStat;
108
+ try {
109
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- path constructed from validated baseDir + entries
110
+ targetStat = fs.statSync(fullPath);
111
+ }
112
+ catch {
113
+ // Skip broken symlinks
114
+ return;
115
+ }
116
+ if (targetStat.isDirectory()) {
117
+ walkDirectory(fullPath);
118
+ }
119
+ else if (targetStat.isFile()) {
120
+ addToResults(normalizedPath, fullPath, relativePath);
121
+ }
122
+ }
123
+ /**
124
+ * Process a directory entry
125
+ */
126
+ function processDirectory(fullPath, normalizedPath, relativePath) {
127
+ // Recurse into subdirectory
128
+ walkDirectory(fullPath);
129
+ // Add directory to results if not filesOnly
130
+ if (!filesOnly) {
131
+ addToResults(normalizedPath, fullPath, relativePath);
132
+ }
133
+ }
134
+ /**
135
+ * Process a file entry
136
+ */
137
+ function processFile(normalizedPath, fullPath, relativePath) {
138
+ addToResults(normalizedPath, fullPath, relativePath);
139
+ }
140
+ /**
141
+ * Recursively walk directory tree
142
+ */
143
+ function walkDirectory(currentDir) {
144
+ let entries;
145
+ try {
146
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- path constructed from validated baseDir, recursively walking
147
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
148
+ }
149
+ catch {
150
+ // Skip directories we don't have permission to read
151
+ return;
152
+ }
153
+ for (const entry of entries) {
154
+ const fullPath = path.join(currentDir, entry.name);
155
+ const relativePath = path.relative(resolvedBaseDir, fullPath);
156
+ const normalizedPath = relativePath.split(path.sep).join('/');
157
+ // Skip excluded paths
158
+ if (shouldExclude(normalizedPath, fullPath)) {
159
+ continue;
160
+ }
161
+ // Dispatch to appropriate handler based on entry type
162
+ if (entry.isSymbolicLink()) {
163
+ processSymlink(fullPath, normalizedPath, relativePath);
164
+ }
165
+ else if (entry.isDirectory()) {
166
+ processDirectory(fullPath, normalizedPath, relativePath);
167
+ }
168
+ else if (entry.isFile()) {
169
+ processFile(normalizedPath, fullPath, relativePath);
170
+ }
171
+ }
172
+ }
173
+ // Start recursive walk from base directory
174
+ walkDirectory(resolvedBaseDir);
175
+ return results;
176
+ }
177
+ //# sourceMappingURL=file-crawler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-crawler.js","sourceRoot":"","sources":["../src/file-crawler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAsBzE;;GAEG;AACH,MAAM,eAAe,GAAG,CAAC,oBAAoB,EAAE,YAAY,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;AAE7F;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAqB;IACxD,OAAO,kBAAkB,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAqB;IACtD,MAAM,EACJ,OAAO,EACP,OAAO,GAAG,CAAC,MAAM,CAAC,EAClB,OAAO,GAAG,eAAe,EACzB,cAAc,GAAG,KAAK,EACtB,QAAQ,GAAG,IAAI,EACf,SAAS,GAAG,IAAI,EAChB,gBAAgB,GAAG,IAAI,GACxB,GAAG,OAAO,CAAC;IAEZ,0CAA0C;IAC1C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9C,+BAA+B;IAC/B,wHAAwH;IACxH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,kCAAkC,eAAe,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,gDAAgD;IAChD,oGAAoG;IACpG,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAC9C,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,iCAAiC,eAAe,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,oCAAoC;IACpC,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAC3C,IAAI,OAAO,GAAkB,IAAI,CAAC;IAElC,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;QACvC,IAAI,OAAO,EAAE,CAAC;YACZ,gBAAgB,GAAG,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED,wCAAwC;IACxC,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC;IAElF,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B;;OAEG;IACH,SAAS,aAAa,CAAC,cAAsB,EAAE,QAAgB;QAC7D,kCAAkC;QAClC,IAAI,UAAU,CAAC,cAAc,CAAC,IAAI,UAAU,CAAC,cAAc,GAAG,GAAG,CAAC,EAAE,CAAC;YACnE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,mCAAmC;QACnC,IAAI,gBAAgB,IAAI,OAAO,EAAE,CAAC;YAChC,uDAAuD;YACvD,MAAM,iBAAiB,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC3D,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEtE,IAAI,gBAAgB,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;gBAChD,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,SAAS,YAAY,CAAC,cAAsB,EAAE,QAAgB,EAAE,YAAoB;QAClF,IAAI,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/B,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS,cAAc,CAAC,QAAgB,EAAE,cAAsB,EAAE,YAAoB;QACpF,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,wDAAwD;QACxD,IAAI,UAAoB,CAAC;QACzB,IAAI,CAAC;YACH,wHAAwH;YACxH,UAAU,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;YACvB,OAAO;QACT,CAAC;QAED,IAAI,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7B,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YAC/B,YAAY,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS,gBAAgB,CAAC,QAAgB,EAAE,cAAsB,EAAE,YAAoB;QACtF,4BAA4B;QAC5B,aAAa,CAAC,QAAQ,CAAC,CAAC;QAExB,4CAA4C;QAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,YAAY,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS,WAAW,CAAC,cAAsB,EAAE,QAAgB,EAAE,YAAoB;QACjF,YAAY,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;IACvD,CAAC;IAED;;OAEG;IACH,SAAS,aAAa,CAAC,UAAkB;QACvC,IAAI,OAAoB,CAAC;QAEzB,IAAI,CAAC;YACH,mIAAmI;YACnI,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;YACpD,OAAO;QACT,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACnD,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;YAC9D,MAAM,cAAc,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAE9D,sBAAsB;YACtB,IAAI,aAAa,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,CAAC;gBAC5C,SAAS;YACX,CAAC;YAED,sDAAsD;YACtD,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC3B,cAAc,CAAC,QAAQ,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;YACzD,CAAC;iBAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC/B,gBAAgB,CAAC,QAAQ,EAAE,cAAc,EAAE,YAAY,CAAC,CAAC;YAC3D,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1B,WAAW,CAAC,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;IACH,CAAC;IAED,2CAA2C;IAC3C,aAAa,CAAC,eAAe,CAAC,CAAC;IAE/B,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Filesystem utilities
3
+ */
4
+ /**
5
+ * Recursively copy a directory
6
+ *
7
+ * @param src - Source directory path
8
+ * @param dest - Destination directory path
9
+ *
10
+ * @example
11
+ * await copyDirectory('/source/dir', '/dest/dir');
12
+ */
13
+ export declare function copyDirectory(src: string, dest: string): Promise<void>;
14
+ //# sourceMappingURL=fs-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-utils.d.ts","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB5E"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Filesystem utilities
3
+ */
4
+ import fs from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ /**
7
+ * Recursively copy a directory
8
+ *
9
+ * @param src - Source directory path
10
+ * @param dest - Destination directory path
11
+ *
12
+ * @example
13
+ * await copyDirectory('/source/dir', '/dest/dir');
14
+ */
15
+ export async function copyDirectory(src, dest) {
16
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths from validated sources
17
+ await fs.mkdir(dest, { recursive: true });
18
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- Paths from validated sources
19
+ const entries = await fs.readdir(src, { withFileTypes: true });
20
+ for (const entry of entries) {
21
+ const srcPath = path.join(src, entry.name);
22
+ const destPath = path.join(dest, entry.name);
23
+ if (entry.isDirectory()) {
24
+ await copyDirectory(srcPath, destPath);
25
+ }
26
+ else {
27
+ await fs.copyFile(srcPath, destPath);
28
+ }
29
+ }
30
+ }
31
+ //# sourceMappingURL=fs-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,IAAY;IAC3D,mGAAmG;IACnG,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,mGAAmG;IACnG,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE7C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Check if a file path is ignored by git
3
+ *
4
+ * Uses git check-ignore which respects .gitignore, .git/info/exclude, and global gitignore
5
+ *
6
+ * @param filePath - Absolute or relative path to check
7
+ * @param cwd - Working directory (defaults to process.cwd())
8
+ * @returns true if file is gitignored, false otherwise
9
+ */
10
+ export declare function isGitIgnored(filePath: string, cwd?: string): boolean;
11
+ //# sourceMappingURL=git-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-utils.d.ts","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAsB,GAAG,OAAO,CAkBnF"}
@@ -0,0 +1,30 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import which from 'which';
3
+ /**
4
+ * Check if a file path is ignored by git
5
+ *
6
+ * Uses git check-ignore which respects .gitignore, .git/info/exclude, and global gitignore
7
+ *
8
+ * @param filePath - Absolute or relative path to check
9
+ * @param cwd - Working directory (defaults to process.cwd())
10
+ * @returns true if file is gitignored, false otherwise
11
+ */
12
+ export function isGitIgnored(filePath, cwd = process.cwd()) {
13
+ try {
14
+ // Resolve git path using which for security (avoids PATH manipulation)
15
+ const gitPath = which.sync('git');
16
+ // git check-ignore returns exit code 0 if file is ignored, 1 if not
17
+ const result = spawnSync(gitPath, ['check-ignore', '-q', filePath], {
18
+ cwd,
19
+ encoding: 'utf-8',
20
+ stdio: 'pipe',
21
+ shell: false, // No shell interpreter for security
22
+ });
23
+ return result.status === 0;
24
+ }
25
+ catch {
26
+ // If git is not available or other error, assume not ignored
27
+ return false;
28
+ }
29
+ }
30
+ //# sourceMappingURL=git-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-utils.js","sourceRoot":"","sources":["../src/git-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IACxE,IAAI,CAAC;QACH,uEAAuE;QACvE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAElC,oEAAoE;QACpE,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;YAClE,GAAG;YACH,QAAQ,EAAE,OAAO;YACjB,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,KAAK,EAAE,oCAAoC;SACnD,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;QAC7D,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Utilities for checking if files are gitignored.
3
+ * Used by file-crawler and link validation.
4
+ */
5
+ import { type Ignore } from 'ignore';
6
+ /**
7
+ * Find the git repository root by walking up from the given directory.
8
+ *
9
+ * @param startDir - Directory to start searching from
10
+ * @returns Path to git root, or null if not in a git repository
11
+ */
12
+ export declare function findGitRoot(startDir: string): string | null;
13
+ /**
14
+ * Load and parse .gitignore files from git root to baseDir.
15
+ * Returns an ignore instance configured with all applicable .gitignore rules.
16
+ *
17
+ * @param gitRoot - Git repository root directory
18
+ * @param baseDir - Base directory being checked (optional, defaults to gitRoot)
19
+ * @returns Configured ignore instance, or null if no gitignore files found
20
+ */
21
+ export declare function loadGitignoreRules(gitRoot: string, baseDir?: string): Ignore | null;
22
+ /**
23
+ * Check if a file path is ignored by git.
24
+ *
25
+ * @param filePath - Absolute path to check
26
+ * @param gitRoot - Git repository root (optional, will auto-detect if not provided)
27
+ * @returns True if file is gitignored, false otherwise
28
+ */
29
+ export declare function isGitignored(filePath: string, gitRoot?: string): boolean;
30
+ //# sourceMappingURL=gitignore-checker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore-checker.d.ts","sourceRoot":"","sources":["../src/gitignore-checker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAe,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3D;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqCnF;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAsBxE"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Utilities for checking if files are gitignored.
3
+ * Used by file-crawler and link validation.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import ignore from 'ignore';
8
+ /**
9
+ * Find the git repository root by walking up from the given directory.
10
+ *
11
+ * @param startDir - Directory to start searching from
12
+ * @returns Path to git root, or null if not in a git repository
13
+ */
14
+ export function findGitRoot(startDir) {
15
+ let currentDir = path.resolve(startDir);
16
+ const root = path.parse(currentDir).root;
17
+ while (currentDir !== root) {
18
+ const gitDir = path.join(currentDir, '.git');
19
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- walking up from validated startDir
20
+ if (fs.existsSync(gitDir)) {
21
+ return currentDir;
22
+ }
23
+ currentDir = path.dirname(currentDir);
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Load and parse .gitignore files from git root to baseDir.
29
+ * Returns an ignore instance configured with all applicable .gitignore rules.
30
+ *
31
+ * @param gitRoot - Git repository root directory
32
+ * @param baseDir - Base directory being checked (optional, defaults to gitRoot)
33
+ * @returns Configured ignore instance, or null if no gitignore files found
34
+ */
35
+ export function loadGitignoreRules(gitRoot, baseDir) {
36
+ const ig = ignore();
37
+ let hasRules = false;
38
+ // Always ignore .git directory
39
+ ig.add('.git');
40
+ hasRules = true;
41
+ // Collect all directories from gitRoot to baseDir
42
+ const dirsToCheck = [];
43
+ let currentDir = path.resolve(baseDir ?? gitRoot);
44
+ const resolvedGitRoot = path.resolve(gitRoot);
45
+ while (currentDir.startsWith(resolvedGitRoot)) {
46
+ dirsToCheck.unshift(currentDir);
47
+ if (currentDir === resolvedGitRoot) {
48
+ break;
49
+ }
50
+ currentDir = path.dirname(currentDir);
51
+ }
52
+ // Load .gitignore files from git root down to baseDir
53
+ for (const dir of dirsToCheck) {
54
+ const gitignorePath = path.join(dir, '.gitignore');
55
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- constructed from validated gitRoot and baseDir
56
+ if (fs.existsSync(gitignorePath)) {
57
+ try {
58
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- validated above
59
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
60
+ ig.add(content);
61
+ }
62
+ catch {
63
+ // Skip gitignore files we can't read
64
+ }
65
+ }
66
+ }
67
+ return hasRules ? ig : null;
68
+ }
69
+ /**
70
+ * Check if a file path is ignored by git.
71
+ *
72
+ * @param filePath - Absolute path to check
73
+ * @param gitRoot - Git repository root (optional, will auto-detect if not provided)
74
+ * @returns True if file is gitignored, false otherwise
75
+ */
76
+ export function isGitignored(filePath, gitRoot) {
77
+ const resolvedPath = path.resolve(filePath);
78
+ // Find git root if not provided
79
+ const root = gitRoot ?? findGitRoot(resolvedPath);
80
+ if (!root) {
81
+ // Not in a git repository
82
+ return false;
83
+ }
84
+ // Load gitignore rules
85
+ const ig = loadGitignoreRules(root);
86
+ if (!ig) {
87
+ // No gitignore rules
88
+ return false;
89
+ }
90
+ // Get path relative to git root
91
+ const relativePath = path.relative(root, resolvedPath);
92
+ const normalizedPath = relativePath.split(path.sep).join('/');
93
+ return ig.ignores(normalizedPath);
94
+ }
95
+ //# sourceMappingURL=gitignore-checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitignore-checker.js","sourceRoot":"","sources":["../src/gitignore-checker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,MAAuB,MAAM,QAAQ,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,IAAI,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;IAEzC,OAAO,UAAU,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC7C,yGAAyG;QACzG,IAAI,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,OAAgB;IAClE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACpB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,+BAA+B;IAC/B,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACf,QAAQ,GAAG,IAAI,CAAC;IAEhB,kDAAkD;IAClD,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;IAClD,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9C,OAAO,UAAU,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9C,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,UAAU,KAAK,eAAe,EAAE,CAAC;YACnC,MAAM;QACR,CAAC;QACD,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,CAAC;IAED,sDAAsD;IACtD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QACnD,qHAAqH;QACrH,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,sFAAsF;gBACtF,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;gBACxD,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;YACvC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,OAAgB;IAC7D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE5C,gCAAgC;IAChC,MAAM,IAAI,GAAG,OAAO,IAAI,WAAW,CAAC,YAAY,CAAC,CAAC;IAClD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,0BAA0B;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uBAAuB;IACvB,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,qBAAqB;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,gCAAgC;IAChC,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE9D,OAAO,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @vibe-agent-toolkit/utils
3
+ * Core shared utilities with no dependencies on other packages
4
+ *
5
+ * Utilities are added as needed by other packages, not speculatively.
6
+ */
7
+ export * from './safe-exec.js';
8
+ export * from './path-utils.js';
9
+ export * from './fs-utils.js';
10
+ export * from './file-crawler.js';
11
+ export * from './gitignore-checker.js';
12
+ export * from './git-utils.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,gBAAgB,CAAC;AAG/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,eAAe,CAAC;AAG9B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @vibe-agent-toolkit/utils
3
+ * Core shared utilities with no dependencies on other packages
4
+ *
5
+ * Utilities are added as needed by other packages, not speculatively.
6
+ */
7
+ // Safe command execution (cross-platform, no shell injection)
8
+ export * from './safe-exec.js';
9
+ // Cross-platform path utilities
10
+ export * from './path-utils.js';
11
+ // Filesystem utilities
12
+ export * from './fs-utils.js';
13
+ // Directory crawling with glob patterns
14
+ export * from './file-crawler.js';
15
+ // Git ignore checking
16
+ export * from './gitignore-checker.js';
17
+ // Git utilities (using git commands directly)
18
+ export * from './git-utils.js';
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,8DAA8D;AAC9D,cAAc,gBAAgB,CAAC;AAE/B,gCAAgC;AAChC,cAAc,iBAAiB,CAAC;AAEhC,uBAAuB;AACvB,cAAc,eAAe,CAAC;AAE9B,wCAAwC;AACxC,cAAc,mBAAmB,CAAC;AAElC,sBAAsB;AACtB,cAAc,wBAAwB,CAAC;AAEvC,8CAA8C;AAC9C,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Normalize a path for cross-platform comparison
3
+ *
4
+ * - Converts to absolute path (if baseDir provided)
5
+ * - Normalizes separators (/ vs \)
6
+ * - Resolves . and ..
7
+ * - Removes trailing slashes
8
+ *
9
+ * @param p - Path to normalize
10
+ * @param baseDir - Optional base directory for relative path resolution
11
+ * @returns Normalized absolute path
12
+ *
13
+ * @example
14
+ * normalizePath('./docs/../README.md', '/project')
15
+ * // Returns: '/project/README.md'
16
+ */
17
+ export declare function normalizePath(p: string, baseDir?: string): string;
18
+ /**
19
+ * Check if a path is absolute
20
+ *
21
+ * Cross-platform detection of absolute paths:
22
+ * - Unix: /path/to/file
23
+ * - Windows: C:\path\to\file or C:/path/to/file
24
+ *
25
+ * @param p - Path to check
26
+ * @returns True if path is absolute
27
+ *
28
+ * @example
29
+ * isAbsolutePath('/path/to/file') // true
30
+ * isAbsolutePath('./relative') // false
31
+ * isAbsolutePath('C:/Windows') // true (Windows)
32
+ */
33
+ export declare function isAbsolutePath(p: string): boolean;
34
+ /**
35
+ * Convert a relative path to absolute
36
+ *
37
+ * If path is already absolute, returns it normalized.
38
+ * Otherwise resolves relative to baseDir.
39
+ *
40
+ * @param p - Path to convert
41
+ * @param baseDir - Base directory for resolution
42
+ * @returns Absolute path
43
+ *
44
+ * @example
45
+ * toAbsolutePath('./docs/README.md', '/project')
46
+ * // Returns: '/project/docs/README.md'
47
+ *
48
+ * toAbsolutePath('/absolute/path.md', '/project')
49
+ * // Returns: '/absolute/path.md'
50
+ */
51
+ export declare function toAbsolutePath(p: string, baseDir: string): string;
52
+ /**
53
+ * Get the relative path from one file to another
54
+ *
55
+ * Useful for generating relative links between markdown files.
56
+ *
57
+ * @param from - Source file path (absolute)
58
+ * @param to - Target file path (absolute)
59
+ * @returns Relative path from source to target
60
+ *
61
+ * @example
62
+ * getRelativePath('/project/docs/guide.md', '/project/README.md')
63
+ * // Returns: '../README.md'
64
+ *
65
+ * getRelativePath('/project/README.md', '/project/docs/api.md')
66
+ * // Returns: 'docs/api.md'
67
+ */
68
+ export declare function getRelativePath(from: string, to: string): string;
69
+ /**
70
+ * Convert a path to Unix-style forward slashes
71
+ *
72
+ * Useful for glob pattern matching, which expects forward slashes.
73
+ * On Windows, path.resolve() and path.normalize() return backslashes,
74
+ * but glob matchers like picomatch expect forward slashes by default.
75
+ *
76
+ * @param p - Path to convert
77
+ * @returns Path with forward slashes
78
+ *
79
+ * @example
80
+ * toUnixPath('C:\\Users\\docs\\README.md')
81
+ * // Returns: 'C:/Users/docs/README.md'
82
+ *
83
+ * toUnixPath('/project/docs/README.md')
84
+ * // Returns: '/project/docs/README.md' (unchanged on Unix)
85
+ */
86
+ export declare function toUnixPath(p: string): string;
87
+ //# sourceMappingURL=path-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAWjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAKjE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5C"}