@vibe-agent-toolkit/resources 0.1.0-rc.7

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.
Files changed (42) hide show
  1. package/README.md +646 -0
  2. package/dist/index.d.ts +33 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +37 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/link-parser.d.ts +37 -0
  7. package/dist/link-parser.d.ts.map +1 -0
  8. package/dist/link-parser.js +327 -0
  9. package/dist/link-parser.js.map +1 -0
  10. package/dist/link-validator.d.ts +30 -0
  11. package/dist/link-validator.d.ts.map +1 -0
  12. package/dist/link-validator.js +217 -0
  13. package/dist/link-validator.js.map +1 -0
  14. package/dist/resource-registry.d.ts +278 -0
  15. package/dist/resource-registry.d.ts.map +1 -0
  16. package/dist/resource-registry.js +468 -0
  17. package/dist/resource-registry.js.map +1 -0
  18. package/dist/schemas/resource-metadata.d.ts +137 -0
  19. package/dist/schemas/resource-metadata.d.ts.map +1 -0
  20. package/dist/schemas/resource-metadata.js +61 -0
  21. package/dist/schemas/resource-metadata.js.map +1 -0
  22. package/dist/schemas/validation-result.d.ts +124 -0
  23. package/dist/schemas/validation-result.d.ts.map +1 -0
  24. package/dist/schemas/validation-result.js +47 -0
  25. package/dist/schemas/validation-result.js.map +1 -0
  26. package/dist/types.d.ts +15 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +14 -0
  29. package/dist/types.js.map +1 -0
  30. package/dist/utils.d.ts +18 -0
  31. package/dist/utils.d.ts.map +1 -0
  32. package/dist/utils.js +26 -0
  33. package/dist/utils.js.map +1 -0
  34. package/package.json +60 -0
  35. package/src/index.ts +66 -0
  36. package/src/link-parser.ts +371 -0
  37. package/src/link-validator.ts +275 -0
  38. package/src/resource-registry.ts +559 -0
  39. package/src/schemas/resource-metadata.ts +86 -0
  40. package/src/schemas/validation-result.ts +55 -0
  41. package/src/types.ts +27 -0
  42. package/src/utils.ts +27 -0
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @vibe-agent-toolkit/resources
3
+ *
4
+ * Markdown resource parsing, validation, and link integrity checking.
5
+ *
6
+ * This package provides comprehensive tools for managing collections of markdown resources,
7
+ * extracting links and headings, validating link integrity, and tracking resource relationships.
8
+ *
9
+ * @packageDocumentation
10
+ *
11
+ * @example Basic usage with ResourceRegistry
12
+ * ```typescript
13
+ * import { ResourceRegistry } from '@vibe-agent-toolkit/resources';
14
+ *
15
+ * const registry = new ResourceRegistry();
16
+ *
17
+ * // Add resources
18
+ * await registry.addResource('./README.md');
19
+ * await registry.crawl({ baseDir: './docs' });
20
+ *
21
+ * // Validate all links
22
+ * const result = await registry.validate();
23
+ * if (!result.passed) {
24
+ * console.error(`Found ${result.errorCount} broken links`);
25
+ * }
26
+ * ```
27
+ */
28
+ // Export main ResourceRegistry class
29
+ export { ResourceRegistry, } from './resource-registry.js';
30
+ // Export schemas for external use (e.g., JSON Schema generation, runtime validation)
31
+ export { LinkTypeSchema, HeadingNodeSchema, ResourceLinkSchema, ResourceMetadataSchema, } from './schemas/resource-metadata.js';
32
+ export { ValidationSeveritySchema, ValidationIssueSchema, ValidationResultSchema, } from './schemas/validation-result.js';
33
+ // Export parser interface for advanced use cases
34
+ export { parseMarkdown } from './link-parser.js';
35
+ // Note: link-parser and link-validator internals are NOT exported
36
+ // They are implementation details. Users should use ResourceRegistry API.
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,qCAAqC;AACrC,OAAO,EACL,gBAAgB,GAIjB,MAAM,wBAAwB,CAAC;AAahC,qFAAqF;AACrF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EACL,wBAAwB,EACxB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,gCAAgC,CAAC;AAExC,iDAAiD;AACjD,OAAO,EAAE,aAAa,EAAoB,MAAM,kBAAkB,CAAC;AAEnE,kEAAkE;AAClE,0EAA0E"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Markdown link parser and analyzer.
3
+ *
4
+ * Parses markdown files to extract:
5
+ * - Links (regular, reference-style, autolinks)
6
+ * - Headings (with GitHub-style slugs and nested tree structure)
7
+ * - File size and token estimates
8
+ *
9
+ * Uses unified/remark for robust markdown parsing with GFM support.
10
+ */
11
+ import type { HeadingNode, ResourceLink } from './types.js';
12
+ /**
13
+ * Result of parsing a markdown file.
14
+ */
15
+ export interface ParseResult {
16
+ links: ResourceLink[];
17
+ headings: HeadingNode[];
18
+ content: string;
19
+ sizeBytes: number;
20
+ estimatedTokenCount: number;
21
+ }
22
+ /**
23
+ * Parse a markdown file and extract all links, headings, and metadata.
24
+ *
25
+ * @param filePath - Absolute path to the markdown file
26
+ * @returns Parsed markdown data including links, headings, size, and token estimate
27
+ * @throws Error if file cannot be read or parsed
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const result = await parseMarkdown('/path/to/document.md');
32
+ * console.log(`Found ${result.links.length} links`);
33
+ * console.log(`Document has ${result.headings.length} top-level headings`);
34
+ * ```
35
+ */
36
+ export declare function parseMarkdown(filePath: string): Promise<ParseResult>;
37
+ //# sourceMappingURL=link-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-parser.d.ts","sourceRoot":"","sources":["../src/link-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,OAAO,KAAK,EAAE,WAAW,EAAY,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC1E"}
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Markdown link parser and analyzer.
3
+ *
4
+ * Parses markdown files to extract:
5
+ * - Links (regular, reference-style, autolinks)
6
+ * - Headings (with GitHub-style slugs and nested tree structure)
7
+ * - File size and token estimates
8
+ *
9
+ * Uses unified/remark for robust markdown parsing with GFM support.
10
+ */
11
+ import { readFile, stat } from 'node:fs/promises';
12
+ import remarkFrontmatter from 'remark-frontmatter';
13
+ import remarkGfm from 'remark-gfm';
14
+ import remarkParse from 'remark-parse';
15
+ import { unified } from 'unified';
16
+ import { visit } from 'unist-util-visit';
17
+ /**
18
+ * Parse a markdown file and extract all links, headings, and metadata.
19
+ *
20
+ * @param filePath - Absolute path to the markdown file
21
+ * @returns Parsed markdown data including links, headings, size, and token estimate
22
+ * @throws Error if file cannot be read or parsed
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const result = await parseMarkdown('/path/to/document.md');
27
+ * console.log(`Found ${result.links.length} links`);
28
+ * console.log(`Document has ${result.headings.length} top-level headings`);
29
+ * ```
30
+ */
31
+ export async function parseMarkdown(filePath) {
32
+ // Read file content and stats
33
+ const [content, stats] = await Promise.all([
34
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- filePath is user-provided path parameter
35
+ readFile(filePath, 'utf-8'),
36
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- filePath is user-provided path parameter
37
+ stat(filePath),
38
+ ]);
39
+ const sizeBytes = stats.size;
40
+ const estimatedTokenCount = Math.ceil(content.length / 4);
41
+ // Parse markdown with unified/remark
42
+ const processor = unified()
43
+ .use(remarkParse)
44
+ .use(remarkGfm)
45
+ .use(remarkFrontmatter);
46
+ const tree = processor.parse(content);
47
+ // Extract links
48
+ const links = extractLinks(tree);
49
+ // Extract headings with tree structure
50
+ const headings = extractHeadings(tree);
51
+ return {
52
+ links,
53
+ headings,
54
+ content,
55
+ sizeBytes,
56
+ estimatedTokenCount,
57
+ };
58
+ }
59
+ /**
60
+ * Extract all links from the markdown AST.
61
+ *
62
+ * Handles:
63
+ * - Regular links: [text](href)
64
+ * - Reference-style links: [text][ref]
65
+ * - Autolinks: <url>
66
+ *
67
+ * @param tree - Markdown AST from unified/remark
68
+ * @returns Array of classified links with line numbers
69
+ */
70
+ function extractLinks(tree) {
71
+ const links = [];
72
+ // Visit link nodes (regular links and autolinks)
73
+ visit(tree, 'link', (node) => {
74
+ const link = {
75
+ text: extractLinkText(node),
76
+ href: node.url,
77
+ type: classifyLink(node.url),
78
+ line: node.position?.start.line,
79
+ };
80
+ links.push(link);
81
+ });
82
+ // Visit linkReference nodes (reference-style links)
83
+ visit(tree, 'linkReference', (node) => {
84
+ // For reference-style links, we use the identifier as href
85
+ // In a full implementation, we'd resolve the definition, but for now
86
+ // we'll classify based on the identifier pattern
87
+ const href = node.identifier;
88
+ const link = {
89
+ text: extractLinkText(node),
90
+ href,
91
+ type: 'unknown', // Reference links need definition resolution
92
+ line: node.position?.start.line,
93
+ };
94
+ links.push(link);
95
+ });
96
+ return links;
97
+ }
98
+ /**
99
+ * Extract text content from a link node.
100
+ *
101
+ * @param node - Link or LinkReference node
102
+ * @returns Text content of the link
103
+ */
104
+ function extractLinkText(node) {
105
+ return extractTextFromChildren(node.children);
106
+ }
107
+ /**
108
+ * Classify a link based on its href.
109
+ *
110
+ * @param href - The href attribute from the link
111
+ * @returns Classified link type
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * classifyLink('https://example.com') // 'external'
116
+ * classifyLink('mailto:user@example.com') // 'email'
117
+ * classifyLink('#heading') // 'anchor'
118
+ * classifyLink('./file.md') // 'local_file'
119
+ * classifyLink('./file.md#anchor') // 'local_file'
120
+ * ```
121
+ */
122
+ function classifyLink(href) {
123
+ if (href.startsWith('http://') || href.startsWith('https://')) {
124
+ return 'external';
125
+ }
126
+ if (href.startsWith('mailto:')) {
127
+ return 'email';
128
+ }
129
+ if (href.startsWith('#')) {
130
+ return 'anchor';
131
+ }
132
+ // Links with anchors are still local file links
133
+ if (href.includes('#')) {
134
+ return 'local_file';
135
+ }
136
+ // .md files are always local files
137
+ if (href.endsWith('.md')) {
138
+ return 'local_file';
139
+ }
140
+ // Paths that look like file paths (start with ./ or ../ or /) or have no extension
141
+ if (href.startsWith('./') || href.startsWith('../') || href.startsWith('/')) {
142
+ return 'local_file';
143
+ }
144
+ // Paths without extensions (no dot or last dot is before a slash)
145
+ const lastSlash = href.lastIndexOf('/');
146
+ const lastDot = href.lastIndexOf('.');
147
+ if (lastDot === -1 || lastDot < lastSlash) {
148
+ return 'local_file';
149
+ }
150
+ return 'unknown';
151
+ }
152
+ /**
153
+ * Extract headings from the markdown AST and build a nested tree structure.
154
+ *
155
+ * Builds a hierarchical structure where:
156
+ * - h2 nodes are children of the preceding h1
157
+ * - h3 nodes are children of the preceding h2
158
+ * - etc.
159
+ *
160
+ * @param tree - Markdown AST from unified/remark
161
+ * @returns Array of top-level heading nodes with nested children
162
+ *
163
+ * @example
164
+ * For markdown:
165
+ * ```
166
+ * # Main
167
+ * ## Sub
168
+ * ### Deep
169
+ * ## Sub2
170
+ * ```
171
+ *
172
+ * Returns:
173
+ * ```
174
+ * [
175
+ * {
176
+ * level: 1,
177
+ * text: 'Main',
178
+ * slug: 'main',
179
+ * children: [
180
+ * { level: 2, text: 'Sub', slug: 'sub', children: [
181
+ * { level: 3, text: 'Deep', slug: 'deep', children: [] }
182
+ * ]},
183
+ * { level: 2, text: 'Sub2', slug: 'sub2', children: [] }
184
+ * ]
185
+ * }
186
+ * ]
187
+ * ```
188
+ */
189
+ function extractHeadings(tree) {
190
+ const flatHeadings = [];
191
+ // First pass: collect all headings in document order
192
+ visit(tree, 'heading', (node) => {
193
+ const text = extractHeadingText(node);
194
+ const heading = {
195
+ level: node.depth,
196
+ text,
197
+ slug: generateSlug(text),
198
+ line: node.position?.start.line,
199
+ };
200
+ flatHeadings.push(heading);
201
+ });
202
+ // Second pass: build tree structure using a stack
203
+ return buildHeadingTree(flatHeadings);
204
+ }
205
+ /**
206
+ * Extract text content from a heading node.
207
+ *
208
+ * @param node - Heading node
209
+ * @returns Text content of the heading
210
+ */
211
+ function extractHeadingText(node) {
212
+ return extractTextFromChildren(node.children);
213
+ }
214
+ /**
215
+ * Extract text content from inline children nodes.
216
+ *
217
+ * Handles text nodes, inline code, emphasis, and other inline elements.
218
+ *
219
+ * @param children - Array of child nodes or undefined
220
+ * @returns Concatenated text content
221
+ */
222
+ function extractTextFromChildren(children) {
223
+ if (!children || children.length === 0) {
224
+ return '';
225
+ }
226
+ return children
227
+ .map((child) => {
228
+ if (child.type === 'text') {
229
+ return child.value;
230
+ }
231
+ // Handle other inline elements (code, emphasis, etc.)
232
+ if ('value' in child) {
233
+ return String(child.value);
234
+ }
235
+ return '';
236
+ })
237
+ .join('');
238
+ }
239
+ /**
240
+ * Generate a GitHub-style slug from heading text.
241
+ *
242
+ * Rules:
243
+ * - Convert to lowercase
244
+ * - Replace spaces with hyphens
245
+ * - Remove special characters
246
+ * - Collapse multiple hyphens
247
+ *
248
+ * @param text - Heading text
249
+ * @returns GitHub-style slug for anchor links
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * generateSlug('Hello World') // 'hello-world'
254
+ * generateSlug('Section 1.1') // 'section-11'
255
+ * generateSlug('API Reference (v2)') // 'api-reference-v2'
256
+ * ```
257
+ */
258
+ function generateSlug(text) {
259
+ return text
260
+ .toLowerCase()
261
+ .trim()
262
+ .replaceAll(/[^\w\s-]/g, '') // Remove special chars
263
+ .replaceAll(/\s+/g, '-') // Replace spaces with hyphens
264
+ .replaceAll(/-+/g, '-'); // Collapse multiple hyphens
265
+ }
266
+ /**
267
+ * Build a nested heading tree from a flat list of headings.
268
+ *
269
+ * Uses a stack-based algorithm to correctly nest headings:
270
+ * - When encountering a higher-level heading, pop stack until we find the parent
271
+ * - Add the heading as a child of the top of stack
272
+ * - Push the heading onto the stack
273
+ *
274
+ * @param flatHeadings - Array of headings in document order
275
+ * @returns Array of top-level headings with nested children
276
+ */
277
+ function buildHeadingTree(flatHeadings) {
278
+ if (flatHeadings.length === 0) {
279
+ return [];
280
+ }
281
+ const roots = [];
282
+ const stack = [];
283
+ for (const heading of flatHeadings) {
284
+ // Initialize children array
285
+ const headingWithChildren = {
286
+ ...heading,
287
+ children: [],
288
+ };
289
+ // Pop stack until we find a heading with lower level (the parent)
290
+ while (stack.length > 0 && (stack.at(-1)?.level ?? 0) >= heading.level) {
291
+ stack.pop();
292
+ }
293
+ if (stack.length === 0) {
294
+ // This is a root-level heading
295
+ roots.push(headingWithChildren);
296
+ }
297
+ else {
298
+ // Add as child of the top of stack
299
+ const parent = stack.at(-1);
300
+ if (parent) {
301
+ parent.children ??= [];
302
+ parent.children.push(headingWithChildren);
303
+ }
304
+ }
305
+ // Push current heading onto stack
306
+ stack.push(headingWithChildren);
307
+ }
308
+ // Clean up empty children arrays (convert to undefined)
309
+ cleanupEmptyChildren(roots);
310
+ return roots;
311
+ }
312
+ /**
313
+ * Remove empty children arrays from heading tree (convert to undefined).
314
+ *
315
+ * @param headings - Array of headings to clean up
316
+ */
317
+ function cleanupEmptyChildren(headings) {
318
+ for (const heading of headings) {
319
+ if (heading.children?.length === 0) {
320
+ heading.children = undefined;
321
+ }
322
+ else if (heading.children && heading.children.length > 0) {
323
+ cleanupEmptyChildren(heading.children);
324
+ }
325
+ }
326
+ }
327
+ //# sourceMappingURL=link-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-parser.js","sourceRoot":"","sources":["../src/link-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Link validation for markdown resources.
3
+ *
4
+ * Validates different types of links:
5
+ * - local_file: Checks if file exists, validates anchors if present
6
+ * - anchor: Validates heading exists in current or target file
7
+ * - external: Returns info (not validated)
8
+ * - email: Returns null (valid by default)
9
+ * - unknown: Returns warning
10
+ */
11
+ import type { ValidationIssue } from './schemas/validation-result.js';
12
+ import type { HeadingNode, ResourceLink } from './types.js';
13
+ /**
14
+ * Validate a single link in a markdown resource.
15
+ *
16
+ * @param link - The link to validate
17
+ * @param sourceFilePath - Absolute path to the file containing the link
18
+ * @param headingsByFile - Map of file paths to their heading trees
19
+ * @returns ValidationIssue if link is broken, null if valid
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const issue = await validateLink(link, '/project/docs/guide.md', headingsMap);
24
+ * if (issue) {
25
+ * console.log(`${issue.severity}: ${issue.message}`);
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function validateLink(link: ResourceLink, sourceFilePath: string, headingsByFile: Map<string, HeadingNode[]>): Promise<ValidationIssue | null>;
30
+ //# sourceMappingURL=link-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-validator.d.ts","sourceRoot":"","sources":["../src/link-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG5D;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,EAClB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GACzC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAuCjC"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Link validation for markdown resources.
3
+ *
4
+ * Validates different types of links:
5
+ * - local_file: Checks if file exists, validates anchors if present
6
+ * - anchor: Validates heading exists in current or target file
7
+ * - external: Returns info (not validated)
8
+ * - email: Returns null (valid by default)
9
+ * - unknown: Returns warning
10
+ */
11
+ import fs from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import { isGitignored } from '@vibe-agent-toolkit/utils';
14
+ import { splitHrefAnchor } from './utils.js';
15
+ /**
16
+ * Validate a single link in a markdown resource.
17
+ *
18
+ * @param link - The link to validate
19
+ * @param sourceFilePath - Absolute path to the file containing the link
20
+ * @param headingsByFile - Map of file paths to their heading trees
21
+ * @returns ValidationIssue if link is broken, null if valid
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const issue = await validateLink(link, '/project/docs/guide.md', headingsMap);
26
+ * if (issue) {
27
+ * console.log(`${issue.severity}: ${issue.message}`);
28
+ * }
29
+ * ```
30
+ */
31
+ export async function validateLink(link, sourceFilePath, headingsByFile) {
32
+ switch (link.type) {
33
+ case 'local_file':
34
+ return await validateLocalFileLink(link, sourceFilePath, headingsByFile);
35
+ case 'anchor':
36
+ return await validateAnchorLink(link, sourceFilePath, headingsByFile);
37
+ case 'external':
38
+ // External URLs are not validated - return info
39
+ return {
40
+ severity: 'info',
41
+ resourcePath: sourceFilePath,
42
+ line: link.line,
43
+ type: 'external_url',
44
+ link: link.href,
45
+ message: 'External URL not validated',
46
+ };
47
+ case 'email':
48
+ // Email links are valid by default
49
+ return null;
50
+ case 'unknown':
51
+ return {
52
+ severity: 'warning',
53
+ resourcePath: sourceFilePath,
54
+ line: link.line,
55
+ type: 'unknown_link',
56
+ link: link.href,
57
+ message: 'Unknown link type',
58
+ };
59
+ default: {
60
+ // TypeScript exhaustiveness check
61
+ const _exhaustive = link.type;
62
+ return _exhaustive;
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Validate a local file link (with optional anchor).
68
+ */
69
+ async function validateLocalFileLink(link, sourceFilePath, headingsByFile) {
70
+ // Extract file path and anchor from href
71
+ const [filePath, anchor] = splitHrefAnchor(link.href);
72
+ // Validate the file exists
73
+ const fileResult = await validateLocalFile(filePath, sourceFilePath);
74
+ if (!fileResult.exists) {
75
+ return {
76
+ severity: 'error',
77
+ resourcePath: sourceFilePath,
78
+ line: link.line,
79
+ type: 'broken_file',
80
+ link: link.href,
81
+ message: `File not found: ${fileResult.resolvedPath}`,
82
+ suggestion: 'Check that the file path is correct and the file exists',
83
+ };
84
+ }
85
+ // Check if the file is gitignored
86
+ if (fileResult.isGitignored) {
87
+ return {
88
+ severity: 'error',
89
+ resourcePath: sourceFilePath,
90
+ line: link.line,
91
+ type: 'broken_file',
92
+ link: link.href,
93
+ message: `File is gitignored: ${fileResult.resolvedPath}`,
94
+ suggestion: 'Gitignored files are local-only and will not exist in the repository. Remove this link or unignore the target file.',
95
+ };
96
+ }
97
+ // If there's an anchor, validate it too
98
+ if (anchor) {
99
+ const anchorValid = await validateAnchor(anchor, fileResult.resolvedPath, headingsByFile);
100
+ if (!anchorValid) {
101
+ return {
102
+ severity: 'error',
103
+ resourcePath: sourceFilePath,
104
+ line: link.line,
105
+ type: 'broken_anchor',
106
+ link: link.href,
107
+ message: `Anchor not found: #${anchor} in ${fileResult.resolvedPath}`,
108
+ suggestion: 'Check that the heading exists in the target file',
109
+ };
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ /**
115
+ * Validate an anchor link (within current file).
116
+ */
117
+ async function validateAnchorLink(link, sourceFilePath, headingsByFile) {
118
+ // Extract anchor (strip leading #)
119
+ const anchor = link.href.startsWith('#') ? link.href.slice(1) : link.href;
120
+ // Validate anchor exists in current file
121
+ const isValid = await validateAnchor(anchor, sourceFilePath, headingsByFile);
122
+ if (!isValid) {
123
+ return {
124
+ severity: 'error',
125
+ resourcePath: sourceFilePath,
126
+ line: link.line,
127
+ type: 'broken_anchor',
128
+ link: link.href,
129
+ message: `Anchor not found: ${link.href}`,
130
+ suggestion: 'Check that the heading exists in this file',
131
+ };
132
+ }
133
+ return null;
134
+ }
135
+ /**
136
+ * Validate that a local file exists and is not gitignored.
137
+ *
138
+ * @param href - The href to the file (relative or absolute)
139
+ * @param sourceFilePath - Absolute path to the source file
140
+ * @returns Object with exists flag, resolved absolute path, and gitignored flag
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const result = await validateLocalFile('./docs/guide.md', '/project/README.md');
145
+ * if (result.exists && !result.isGitignored) {
146
+ * console.log('File exists at:', result.resolvedPath);
147
+ * }
148
+ * ```
149
+ */
150
+ async function validateLocalFile(href, sourceFilePath) {
151
+ // Resolve the path relative to the source file's directory
152
+ const sourceDir = path.dirname(sourceFilePath);
153
+ const resolvedPath = path.resolve(sourceDir, href);
154
+ // Check if file exists
155
+ let exists = false;
156
+ try {
157
+ await fs.access(resolvedPath, fs.constants.F_OK);
158
+ exists = true;
159
+ }
160
+ catch {
161
+ exists = false;
162
+ }
163
+ // Check if file is gitignored (only if it exists)
164
+ const gitignored = exists && isGitignored(resolvedPath);
165
+ return { exists, resolvedPath, isGitignored: gitignored };
166
+ }
167
+ /**
168
+ * Validate that an anchor (heading slug) exists in a file.
169
+ *
170
+ * @param anchor - The heading slug to find (without leading #)
171
+ * @param targetFilePath - Absolute path to the file containing the heading
172
+ * @param headingsByFile - Map of file paths to their heading trees
173
+ * @returns True if anchor exists, false otherwise
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const valid = await validateAnchor('my-heading', '/project/docs/guide.md', headingsMap);
178
+ * ```
179
+ */
180
+ async function validateAnchor(anchor, targetFilePath, headingsByFile) {
181
+ // Get headings for target file
182
+ const headings = headingsByFile.get(targetFilePath);
183
+ if (!headings) {
184
+ return false;
185
+ }
186
+ // Search for matching slug (case-insensitive)
187
+ return findHeadingBySlug(headings, anchor);
188
+ }
189
+ /**
190
+ * Recursively search heading tree for a matching slug.
191
+ *
192
+ * Performs case-insensitive comparison of slugs.
193
+ *
194
+ * @param headings - Array of heading nodes to search
195
+ * @param targetSlug - The slug to find
196
+ * @returns True if slug found, false otherwise
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * const found = findHeadingBySlug(headings, 'my-heading');
201
+ * ```
202
+ */
203
+ function findHeadingBySlug(headings, targetSlug) {
204
+ const normalizedTarget = targetSlug.toLowerCase();
205
+ for (const heading of headings) {
206
+ // Check current heading
207
+ if (heading.slug.toLowerCase() === normalizedTarget) {
208
+ return true;
209
+ }
210
+ // Recursively check children
211
+ if (heading.children && findHeadingBySlug(heading.children, targetSlug)) {
212
+ return true;
213
+ }
214
+ }
215
+ return false;
216
+ }
217
+ //# sourceMappingURL=link-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-validator.js","sourceRoot":"","sources":["../src/link-validator.ts"],"names":[],"mappings":"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"}