@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.
- package/README.md +646 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/link-parser.d.ts +37 -0
- package/dist/link-parser.d.ts.map +1 -0
- package/dist/link-parser.js +327 -0
- package/dist/link-parser.js.map +1 -0
- package/dist/link-validator.d.ts +30 -0
- package/dist/link-validator.d.ts.map +1 -0
- package/dist/link-validator.js +217 -0
- package/dist/link-validator.js.map +1 -0
- package/dist/resource-registry.d.ts +278 -0
- package/dist/resource-registry.d.ts.map +1 -0
- package/dist/resource-registry.js +468 -0
- package/dist/resource-registry.js.map +1 -0
- package/dist/schemas/resource-metadata.d.ts +137 -0
- package/dist/schemas/resource-metadata.d.ts.map +1 -0
- package/dist/schemas/resource-metadata.js +61 -0
- package/dist/schemas/resource-metadata.js.map +1 -0
- package/dist/schemas/validation-result.d.ts +124 -0
- package/dist/schemas/validation-result.d.ts.map +1 -0
- package/dist/schemas/validation-result.js +47 -0
- package/dist/schemas/validation-result.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +26 -0
- package/dist/utils.js.map +1 -0
- package/package.json +60 -0
- package/src/index.ts +66 -0
- package/src/link-parser.ts +371 -0
- package/src/link-validator.ts +275 -0
- package/src/resource-registry.ts +559 -0
- package/src/schemas/resource-metadata.ts +86 -0
- package/src/schemas/validation-result.ts +55 -0
- package/src/types.ts +27 -0
- 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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAkB,EAClB,cAAsB,EACtB,cAA0C;IAE1C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,YAAY;YACf,OAAO,MAAM,qBAAqB,CAAC,IAAI,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;QAE3E,KAAK,QAAQ;YACX,OAAO,MAAM,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;QAExE,KAAK,UAAU;YACb,gDAAgD;YAChD,OAAO;gBACL,QAAQ,EAAE,MAAM;gBAChB,YAAY,EAAE,cAAc;gBAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,4BAA4B;aACtC,CAAC;QAEJ,KAAK,OAAO;YACV,mCAAmC;YACnC,OAAO,IAAI,CAAC;QAEd,KAAK,SAAS;YACZ,OAAO;gBACL,QAAQ,EAAE,SAAS;gBACnB,YAAY,EAAE,cAAc;gBAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,cAAc;gBACpB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,mBAAmB;aAC7B,CAAC;QAEJ,OAAO,CAAC,CAAC,CAAC;YACR,kCAAkC;YAClC,MAAM,WAAW,GAAU,IAAI,CAAC,IAAI,CAAC;YACrC,OAAO,WAAW,CAAC;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,qBAAqB,CAClC,IAAkB,EAClB,cAAsB,EACtB,cAA0C;IAE1C,yCAAyC;IACzC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEtD,2BAA2B;IAC3B,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAErE,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACvB,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,YAAY,EAAE,cAAc;YAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,mBAAmB,UAAU,CAAC,YAAY,EAAE;YACrD,UAAU,EAAE,yDAAyD;SACtE,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC;QAC5B,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,YAAY,EAAE,cAAc;YAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,uBAAuB,UAAU,CAAC,YAAY,EAAE;YACzD,UAAU,EACR,qHAAqH;SACxH,CAAC;IACJ,CAAC;IAED,wCAAwC;IACxC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,WAAW,GAAG,MAAM,cAAc,CACtC,MAAM,EACN,UAAU,CAAC,YAAY,EACvB,cAAc,CACf,CAAC;QAEF,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,YAAY,EAAE,cAAc;gBAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,eAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,sBAAsB,MAAM,OAAO,UAAU,CAAC,YAAY,EAAE;gBACrE,UAAU,EAAE,kDAAkD;aAC/D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAC/B,IAAkB,EAClB,cAAsB,EACtB,cAA0C;IAE1C,mCAAmC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAE1E,yCAAyC;IACzC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC;IAE7E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,OAAO;YACjB,YAAY,EAAE,cAAc;YAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,OAAO,EAAE,qBAAqB,IAAI,CAAC,IAAI,EAAE;YACzC,UAAU,EAAE,4CAA4C;SACzD,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAGD;;;;;;;;;;;;;;GAcG;AACH,KAAK,UAAU,iBAAiB,CAC9B,IAAY,EACZ,cAAsB;IAEtB,2DAA2D;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAEnD,uBAAuB;IACvB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,KAAK,CAAC;IACjB,CAAC;IAED,kDAAkD;IAClD,MAAM,UAAU,GAAG,MAAM,IAAI,YAAY,CAAC,YAAY,CAAC,CAAC;IAExD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,UAAU,cAAc,CAC3B,MAAc,EACd,cAAsB,EACtB,cAA0C;IAE1C,+BAA+B;IAC/B,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8CAA8C;IAC9C,OAAO,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAS,iBAAiB,CACxB,QAAuB,EACvB,UAAkB;IAElB,MAAM,gBAAgB,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IAElD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,wBAAwB;QACxB,IAAI,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,gBAAgB,EAAE,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,6BAA6B;QAC7B,IAAI,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;YACxE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|