@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/README.md
ADDED
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
# @vibe-agent-toolkit/resources
|
|
2
|
+
|
|
3
|
+
Markdown resource parsing, validation, and link integrity checking for AI agent toolkits.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Parse markdown files** - Extract links, headings, and metadata using unified/remark
|
|
8
|
+
- **Validate link integrity** - Check local file links, anchor links, and detect broken references
|
|
9
|
+
- **Track resource collections** - Manage multiple markdown files with automatic ID generation
|
|
10
|
+
- **Resolve cross-references** - Link resources together and track dependencies
|
|
11
|
+
- **Query capabilities** - Find resources by path, ID, or glob patterns
|
|
12
|
+
- **GitHub Flavored Markdown** - Full support for GFM including tables, task lists, and autolinks
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add @vibe-agent-toolkit/resources
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { ResourceRegistry } from '@vibe-agent-toolkit/resources';
|
|
24
|
+
|
|
25
|
+
// Create registry
|
|
26
|
+
const registry = new ResourceRegistry();
|
|
27
|
+
|
|
28
|
+
// Add single resource
|
|
29
|
+
await registry.addResource('./README.md');
|
|
30
|
+
|
|
31
|
+
// Crawl directory for all markdown files
|
|
32
|
+
await registry.crawl({
|
|
33
|
+
baseDir: './docs',
|
|
34
|
+
include: ['**/*.md'],
|
|
35
|
+
exclude: ['**/node_modules/**']
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Validate all links
|
|
39
|
+
const result = await registry.validate();
|
|
40
|
+
console.log(`Validated ${result.totalLinks} links in ${result.totalResources} files`);
|
|
41
|
+
console.log(`Status: ${result.passed ? 'PASSED' : 'FAILED'}`);
|
|
42
|
+
console.log(`Errors: ${result.errorCount}, Warnings: ${result.warningCount}`);
|
|
43
|
+
|
|
44
|
+
// Show any issues
|
|
45
|
+
for (const issue of result.issues) {
|
|
46
|
+
console.log(`${issue.severity.toUpperCase()}: ${issue.message}`);
|
|
47
|
+
if (issue.line) console.log(` at ${issue.resourcePath}:${issue.line}`);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API Reference
|
|
52
|
+
|
|
53
|
+
### ResourceRegistry
|
|
54
|
+
|
|
55
|
+
Main class for managing collections of markdown resources.
|
|
56
|
+
|
|
57
|
+
#### Constructor
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
new ResourceRegistry(options?: ResourceRegistryOptions)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Options:**
|
|
64
|
+
- `validateOnAdd?: boolean` - Validate resources immediately when added (default: `false`)
|
|
65
|
+
|
|
66
|
+
#### Methods
|
|
67
|
+
|
|
68
|
+
##### addResource(filePath: string): Promise<ResourceMetadata>
|
|
69
|
+
|
|
70
|
+
Add a single markdown file to the registry.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const resource = await registry.addResource('./docs/guide.md');
|
|
74
|
+
console.log(`Added ${resource.id} with ${resource.links.length} links`);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Parameters:**
|
|
78
|
+
- `filePath` - Path to markdown file (relative or absolute)
|
|
79
|
+
|
|
80
|
+
**Returns:** Parsed resource metadata
|
|
81
|
+
|
|
82
|
+
**Throws:** Error if file cannot be read or parsed
|
|
83
|
+
|
|
84
|
+
##### addResources(filePaths: string[]): Promise<ResourceMetadata[]>
|
|
85
|
+
|
|
86
|
+
Add multiple markdown files in parallel.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const resources = await registry.addResources([
|
|
90
|
+
'./README.md',
|
|
91
|
+
'./docs/api.md',
|
|
92
|
+
'./docs/guide.md'
|
|
93
|
+
]);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
##### crawl(options: CrawlOptions): Promise<ResourceMetadata[]>
|
|
97
|
+
|
|
98
|
+
Crawl a directory and add all matching markdown files.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const resources = await registry.crawl({
|
|
102
|
+
baseDir: './docs',
|
|
103
|
+
include: ['**/*.md'], // Glob patterns (default: ['**/*.md'])
|
|
104
|
+
exclude: ['**/node_modules/**'], // Exclude patterns (default: node_modules, .git, dist)
|
|
105
|
+
followSymlinks: false // Follow symbolic links (default: false)
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Options:**
|
|
110
|
+
- `baseDir` (required) - Base directory to crawl
|
|
111
|
+
- `include` - Include glob patterns (default: `['**/*.md']`)
|
|
112
|
+
- `exclude` - Exclude glob patterns (default: `['**/node_modules/**', '**/.git/**', '**/dist/**']`)
|
|
113
|
+
- `followSymlinks` - Follow symbolic links (default: `false`)
|
|
114
|
+
|
|
115
|
+
##### validate(): Promise<ValidationResult>
|
|
116
|
+
|
|
117
|
+
Validate all links in all resources.
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
const result = await registry.validate();
|
|
121
|
+
|
|
122
|
+
console.log(`Resources: ${result.totalResources}`);
|
|
123
|
+
console.log(`Links: ${result.totalLinks}`);
|
|
124
|
+
console.log(`Errors: ${result.errorCount}`);
|
|
125
|
+
console.log(`Warnings: ${result.warningCount}`);
|
|
126
|
+
console.log(`Info: ${result.infoCount}`);
|
|
127
|
+
console.log(`Passed: ${result.passed}`);
|
|
128
|
+
console.log(`Duration: ${result.durationMs}ms`);
|
|
129
|
+
|
|
130
|
+
// Links by type
|
|
131
|
+
for (const [type, count] of Object.entries(result.linksByType)) {
|
|
132
|
+
console.log(` ${type}: ${count}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Issues
|
|
136
|
+
for (const issue of result.issues) {
|
|
137
|
+
console.log(`[${issue.severity}] ${issue.message}`);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Returns:** Complete validation results with issues and statistics
|
|
142
|
+
|
|
143
|
+
**Validation rules:**
|
|
144
|
+
- `local_file` links - File must exist, anchor must be valid if present
|
|
145
|
+
- `anchor` links - Heading must exist in current file
|
|
146
|
+
- `external` links - Not validated (returns info level issue)
|
|
147
|
+
- `email` links - Valid by default
|
|
148
|
+
- `unknown` links - Returns warning
|
|
149
|
+
|
|
150
|
+
##### resolveLinks(): void
|
|
151
|
+
|
|
152
|
+
Resolve cross-references between resources in the registry.
|
|
153
|
+
|
|
154
|
+
For each `local_file` link, sets the `resolvedId` property to the target resource's ID if it exists in the registry.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
registry.resolveLinks();
|
|
158
|
+
|
|
159
|
+
const resource = registry.getResource('./README.md');
|
|
160
|
+
for (const link of resource.links) {
|
|
161
|
+
if (link.type === 'local_file' && link.resolvedId) {
|
|
162
|
+
console.log(`Link to ${link.href} resolves to resource: ${link.resolvedId}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
##### getResource(filePath: string): ResourceMetadata | undefined
|
|
168
|
+
|
|
169
|
+
Get a resource by its file path.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const resource = registry.getResource('./docs/guide.md');
|
|
173
|
+
if (resource) {
|
|
174
|
+
console.log(`Found: ${resource.id}`);
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
##### getResourceById(id: string): ResourceMetadata | undefined
|
|
179
|
+
|
|
180
|
+
Get a resource by its ID.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const resource = registry.getResourceById('readme');
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Note:** IDs are auto-generated from file names (e.g., `README.md` becomes `readme`, `User Guide.md` becomes `user-guide`).
|
|
187
|
+
|
|
188
|
+
##### getAllResources(): ResourceMetadata[]
|
|
189
|
+
|
|
190
|
+
Get all resources in the registry.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const allResources = registry.getAllResources();
|
|
194
|
+
console.log(`Total: ${allResources.length}`);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
##### getResourcesByPattern(pattern: string): ResourceMetadata[]
|
|
198
|
+
|
|
199
|
+
Get resources matching a glob pattern.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Get all docs
|
|
203
|
+
const docs = registry.getResourcesByPattern('**/docs/**');
|
|
204
|
+
|
|
205
|
+
// Get all READMEs
|
|
206
|
+
const readmes = registry.getResourcesByPattern('**/README.md');
|
|
207
|
+
|
|
208
|
+
// Get specific directory
|
|
209
|
+
const guides = registry.getResourcesByPattern('docs/guides/**');
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
##### getStats(): RegistryStats
|
|
213
|
+
|
|
214
|
+
Get statistics about resources in the registry.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const stats = registry.getStats();
|
|
218
|
+
console.log(`Resources: ${stats.totalResources}`);
|
|
219
|
+
console.log(`Links: ${stats.totalLinks}`);
|
|
220
|
+
console.log(`Local file links: ${stats.linksByType.local_file}`);
|
|
221
|
+
console.log(`External links: ${stats.linksByType.external}`);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Returns:**
|
|
225
|
+
```typescript
|
|
226
|
+
interface RegistryStats {
|
|
227
|
+
totalResources: number;
|
|
228
|
+
totalLinks: number;
|
|
229
|
+
linksByType: Record<string, number>;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
##### clear(): void
|
|
234
|
+
|
|
235
|
+
Clear all resources from the registry.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
registry.clear();
|
|
239
|
+
console.log(registry.getAllResources().length); // 0
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Type Definitions
|
|
243
|
+
|
|
244
|
+
### ResourceMetadata
|
|
245
|
+
|
|
246
|
+
Complete metadata for a markdown resource.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
interface ResourceMetadata {
|
|
250
|
+
id: string; // Unique identifier (auto-generated from file name)
|
|
251
|
+
filePath: string; // Absolute path to file
|
|
252
|
+
links: ResourceLink[]; // All links found in the resource
|
|
253
|
+
headings: HeadingNode[]; // Document table of contents (top-level only, nested via children)
|
|
254
|
+
sizeBytes: number; // File size in bytes
|
|
255
|
+
estimatedTokenCount: number; // Estimated tokens for LLM context (~1 token per 4 chars)
|
|
256
|
+
modifiedAt: Date; // Last modified timestamp
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### ResourceLink
|
|
261
|
+
|
|
262
|
+
Represents a link found in a markdown resource.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
interface ResourceLink {
|
|
266
|
+
text: string; // Link text displayed to users
|
|
267
|
+
href: string; // Raw href attribute from markdown
|
|
268
|
+
type: LinkType; // Classified link type
|
|
269
|
+
line?: number; // Line number in source file
|
|
270
|
+
resolvedPath?: string; // Absolute file path (for local_file links)
|
|
271
|
+
anchorTarget?: string; // Target heading slug (for anchor links)
|
|
272
|
+
resolvedId?: string; // Resolved resource ID (for local_file links, set by resolveLinks())
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### LinkType
|
|
277
|
+
|
|
278
|
+
Type of link found in markdown.
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
type LinkType = 'local_file' | 'anchor' | 'external' | 'email' | 'unknown';
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
- `local_file` - Link to a local file (relative or absolute path)
|
|
285
|
+
- `anchor` - Link to a heading anchor (e.g., `#heading-slug`)
|
|
286
|
+
- `external` - HTTP/HTTPS URL to external resource
|
|
287
|
+
- `email` - Mailto link
|
|
288
|
+
- `unknown` - Unclassified link type
|
|
289
|
+
|
|
290
|
+
### HeadingNode
|
|
291
|
+
|
|
292
|
+
Represents a heading node in the document's table of contents.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
interface HeadingNode {
|
|
296
|
+
level: number; // Heading level (1-6)
|
|
297
|
+
text: string; // Raw text content
|
|
298
|
+
slug: string; // GitHub-style slug for anchor links (lowercase, hyphenated)
|
|
299
|
+
line?: number; // Line number in source file
|
|
300
|
+
children?: HeadingNode[]; // Nested child headings
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Note:** The headings array contains only top-level headings. Child headings are nested under their parents via the `children` property, forming a recursive tree structure.
|
|
305
|
+
|
|
306
|
+
### ValidationResult
|
|
307
|
+
|
|
308
|
+
Complete results from validating a collection of resources.
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
interface ValidationResult {
|
|
312
|
+
totalResources: number; // Total resources validated
|
|
313
|
+
totalLinks: number; // Total links found
|
|
314
|
+
linksByType: Record<string, number>; // Count of links by type
|
|
315
|
+
issues: ValidationIssue[]; // All validation issues
|
|
316
|
+
errorCount: number; // Number of error-level issues
|
|
317
|
+
warningCount: number; // Number of warning-level issues
|
|
318
|
+
infoCount: number; // Number of info-level issues
|
|
319
|
+
passed: boolean; // True if errorCount === 0
|
|
320
|
+
durationMs: number; // Validation duration in milliseconds
|
|
321
|
+
timestamp: Date; // When validation was performed
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### ValidationIssue
|
|
326
|
+
|
|
327
|
+
A single validation issue found during link validation.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
interface ValidationIssue {
|
|
331
|
+
severity: ValidationSeverity; // Issue severity level
|
|
332
|
+
resourcePath: string; // Absolute path to the resource containing the issue
|
|
333
|
+
line?: number; // Line number where the issue occurs
|
|
334
|
+
type: string; // Issue type identifier (e.g., 'broken_file', 'broken_anchor')
|
|
335
|
+
link: string; // The problematic link
|
|
336
|
+
message: string; // Human-readable description
|
|
337
|
+
suggestion?: string; // Optional suggestion for fixing
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### ValidationSeverity
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
type ValidationSeverity = 'error' | 'warning' | 'info';
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
- `error` - Critical issue that should block usage (e.g., broken file link)
|
|
348
|
+
- `warning` - Non-critical issue that should be addressed (e.g., questionable link format)
|
|
349
|
+
- `info` - Informational message (e.g., external URL not validated)
|
|
350
|
+
|
|
351
|
+
## Schemas
|
|
352
|
+
|
|
353
|
+
All types are backed by Zod schemas for runtime validation. You can import schemas for advanced use cases:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import {
|
|
357
|
+
ResourceMetadataSchema,
|
|
358
|
+
ResourceLinkSchema,
|
|
359
|
+
ValidationResultSchema
|
|
360
|
+
} from '@vibe-agent-toolkit/resources';
|
|
361
|
+
|
|
362
|
+
// Runtime validation
|
|
363
|
+
const result = ResourceMetadataSchema.safeParse(data);
|
|
364
|
+
if (result.success) {
|
|
365
|
+
console.log('Valid resource:', result.data);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Convert to JSON Schema
|
|
369
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
370
|
+
const jsonSchema = zodToJsonSchema(ResourceMetadataSchema);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Advanced Usage
|
|
374
|
+
|
|
375
|
+
### Parse Individual Files
|
|
376
|
+
|
|
377
|
+
For advanced use cases, you can use the `parseMarkdown` function directly:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { parseMarkdown } from '@vibe-agent-toolkit/resources';
|
|
381
|
+
|
|
382
|
+
const result = await parseMarkdown('./document.md');
|
|
383
|
+
console.log('Links:', result.links);
|
|
384
|
+
console.log('Headings:', result.headings);
|
|
385
|
+
console.log('Content:', result.content);
|
|
386
|
+
console.log('Size:', result.sizeBytes);
|
|
387
|
+
console.log('Estimated tokens:', result.estimatedTokenCount);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Query Patterns
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// Find all documentation
|
|
394
|
+
const docs = registry.getResourcesByPattern('**/docs/**/*.md');
|
|
395
|
+
|
|
396
|
+
// Find all READMEs
|
|
397
|
+
const readmes = registry.getResourcesByPattern('**/README.md');
|
|
398
|
+
|
|
399
|
+
// Find specific subdirectory
|
|
400
|
+
const apiDocs = registry.getResourcesByPattern('docs/api/**/*.md');
|
|
401
|
+
|
|
402
|
+
// Complex patterns
|
|
403
|
+
const guides = registry.getResourcesByPattern('**/+(guide|tutorial)*.md');
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Handle Validation Errors
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const result = await registry.validate();
|
|
410
|
+
|
|
411
|
+
// Filter by severity
|
|
412
|
+
const errors = result.issues.filter(i => i.severity === 'error');
|
|
413
|
+
const warnings = result.issues.filter(i => i.severity === 'warning');
|
|
414
|
+
|
|
415
|
+
// Group by resource
|
|
416
|
+
const issuesByResource = new Map<string, ValidationIssue[]>();
|
|
417
|
+
for (const issue of result.issues) {
|
|
418
|
+
const issues = issuesByResource.get(issue.resourcePath) ?? [];
|
|
419
|
+
issues.push(issue);
|
|
420
|
+
issuesByResource.set(issue.resourcePath, issues);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Show summary
|
|
424
|
+
for (const [path, issues] of issuesByResource) {
|
|
425
|
+
console.log(`\n${path}:`);
|
|
426
|
+
for (const issue of issues) {
|
|
427
|
+
console.log(` [${issue.severity}] Line ${issue.line}: ${issue.message}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Validate on Add
|
|
433
|
+
|
|
434
|
+
Enable strict validation mode to fail fast on broken links:
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
const registry = new ResourceRegistry({ validateOnAdd: true });
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
await registry.addResource('./docs/broken.md');
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error('Validation failed:', error.message);
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Examples
|
|
447
|
+
|
|
448
|
+
### Validate Project Documentation
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
import { ResourceRegistry } from '@vibe-agent-toolkit/resources';
|
|
452
|
+
|
|
453
|
+
async function validateDocs() {
|
|
454
|
+
const registry = new ResourceRegistry();
|
|
455
|
+
|
|
456
|
+
// Crawl all markdown in project
|
|
457
|
+
await registry.crawl({
|
|
458
|
+
baseDir: process.cwd(),
|
|
459
|
+
exclude: ['**/node_modules/**', '**/dist/**', '**/.git/**']
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Validate
|
|
463
|
+
const result = await registry.validate();
|
|
464
|
+
|
|
465
|
+
if (!result.passed) {
|
|
466
|
+
console.error(`\nValidation failed with ${result.errorCount} errors\n`);
|
|
467
|
+
|
|
468
|
+
for (const issue of result.issues.filter(i => i.severity === 'error')) {
|
|
469
|
+
console.error(`${issue.resourcePath}:${issue.line ?? '?'}`);
|
|
470
|
+
console.error(` ${issue.message}`);
|
|
471
|
+
if (issue.suggestion) {
|
|
472
|
+
console.error(` Suggestion: ${issue.suggestion}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.log(`✓ All links valid (${result.totalLinks} links in ${result.totalResources} files)`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
validateDocs();
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Build Resource Graph
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import { ResourceRegistry } from '@vibe-agent-toolkit/resources';
|
|
489
|
+
|
|
490
|
+
async function buildGraph() {
|
|
491
|
+
const registry = new ResourceRegistry();
|
|
492
|
+
await registry.crawl({ baseDir: './docs' });
|
|
493
|
+
|
|
494
|
+
// Resolve all cross-references
|
|
495
|
+
registry.resolveLinks();
|
|
496
|
+
|
|
497
|
+
// Build dependency graph
|
|
498
|
+
const graph = new Map<string, Set<string>>();
|
|
499
|
+
|
|
500
|
+
for (const resource of registry.getAllResources()) {
|
|
501
|
+
const deps = new Set<string>();
|
|
502
|
+
|
|
503
|
+
for (const link of resource.links) {
|
|
504
|
+
if (link.type === 'local_file' && link.resolvedId) {
|
|
505
|
+
deps.add(link.resolvedId);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
graph.set(resource.id, deps);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Show dependencies
|
|
513
|
+
for (const [id, deps] of graph) {
|
|
514
|
+
if (deps.size > 0) {
|
|
515
|
+
console.log(`${id} depends on: ${[...deps].join(', ')}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
buildGraph();
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Generate Link Report
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
async function linkReport() {
|
|
527
|
+
const registry = new ResourceRegistry();
|
|
528
|
+
await registry.crawl({ baseDir: './docs' });
|
|
529
|
+
|
|
530
|
+
const result = await registry.validate();
|
|
531
|
+
|
|
532
|
+
// Statistics
|
|
533
|
+
console.log('Link Statistics:');
|
|
534
|
+
console.log(` Total: ${result.totalLinks}`);
|
|
535
|
+
for (const [type, count] of Object.entries(result.linksByType)) {
|
|
536
|
+
const percentage = ((count / result.totalLinks) * 100).toFixed(1);
|
|
537
|
+
console.log(` ${type}: ${count} (${percentage}%)`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// External links
|
|
541
|
+
console.log('\nExternal Links:');
|
|
542
|
+
for (const resource of registry.getAllResources()) {
|
|
543
|
+
const externalLinks = resource.links.filter(l => l.type === 'external');
|
|
544
|
+
if (externalLinks.length > 0) {
|
|
545
|
+
console.log(`\n${resource.filePath}:`);
|
|
546
|
+
for (const link of externalLinks) {
|
|
547
|
+
console.log(` - ${link.href}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
linkReport();
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
## How It Works
|
|
557
|
+
|
|
558
|
+
1. **Parsing** - Uses [unified](https://unifiedjs.com/) and [remark](https://remark.js.org/) to parse markdown into an AST (Abstract Syntax Tree)
|
|
559
|
+
2. **Link Extraction** - Traverses AST to find all links (regular, reference-style, autolinks)
|
|
560
|
+
3. **Heading Extraction** - Builds a hierarchical tree of headings with GitHub-style slugs
|
|
561
|
+
4. **Link Classification** - Classifies links as local_file, anchor, external, email, or unknown
|
|
562
|
+
5. **Validation** - Checks file existence, anchor validity, and cross-references
|
|
563
|
+
6. **Resolution** - Maps local_file links to resource IDs for dependency tracking
|
|
564
|
+
|
|
565
|
+
## Link Types
|
|
566
|
+
|
|
567
|
+
### Local File Links
|
|
568
|
+
|
|
569
|
+
```markdown
|
|
570
|
+
[Guide](./guide.md)
|
|
571
|
+
[API](../api/README.md)
|
|
572
|
+
[Doc with anchor](./doc.md#section)
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
Validated by checking:
|
|
576
|
+
- File exists on filesystem
|
|
577
|
+
- If anchor present, heading exists in target file
|
|
578
|
+
|
|
579
|
+
### Anchor Links
|
|
580
|
+
|
|
581
|
+
```markdown
|
|
582
|
+
[Section](#section-name)
|
|
583
|
+
[Heading](#heading-slug)
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
Validated by checking:
|
|
587
|
+
- Heading with matching slug exists in current file
|
|
588
|
+
- Slugs are GitHub-style (lowercase, hyphenated)
|
|
589
|
+
|
|
590
|
+
### External Links
|
|
591
|
+
|
|
592
|
+
```markdown
|
|
593
|
+
[Google](https://google.com)
|
|
594
|
+
[Docs](https://example.com/docs)
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
Not validated (returns info level issue). External URL validation is planned for future releases.
|
|
598
|
+
|
|
599
|
+
### Email Links
|
|
600
|
+
|
|
601
|
+
```markdown
|
|
602
|
+
[Contact](mailto:user@example.com)
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Valid by default.
|
|
606
|
+
|
|
607
|
+
## Platform Support
|
|
608
|
+
|
|
609
|
+
- Cross-platform (Windows, macOS, Linux)
|
|
610
|
+
- Node.js 18+ (ESM only)
|
|
611
|
+
- Bun 1.0+
|
|
612
|
+
|
|
613
|
+
## Dependencies
|
|
614
|
+
|
|
615
|
+
- [unified](https://unifiedjs.com/) - Markdown processing framework
|
|
616
|
+
- [remark-parse](https://github.com/remarkjs/remark/tree/main/packages/remark-parse) - Markdown parser
|
|
617
|
+
- [remark-gfm](https://github.com/remarkjs/remark-gfm) - GitHub Flavored Markdown support
|
|
618
|
+
- [remark-frontmatter](https://github.com/remarkjs/remark-frontmatter) - Front matter support (future use)
|
|
619
|
+
- [unist-util-visit](https://github.com/syntax-tree/unist-util-visit) - AST traversal
|
|
620
|
+
- [picomatch](https://github.com/micromatch/picomatch) - Glob pattern matching
|
|
621
|
+
- [zod](https://zod.dev/) - Schema validation
|
|
622
|
+
|
|
623
|
+
## Related Packages
|
|
624
|
+
|
|
625
|
+
- [@vibe-agent-toolkit/utils](../utils) - Core shared utilities
|
|
626
|
+
|
|
627
|
+
## Future Enhancements
|
|
628
|
+
|
|
629
|
+
Planned features for future releases:
|
|
630
|
+
|
|
631
|
+
- Front matter parsing (title, description, tags)
|
|
632
|
+
- ID override via front matter
|
|
633
|
+
- External URL validation (opt-in HTTP HEAD requests)
|
|
634
|
+
- Circular reference detection
|
|
635
|
+
- Link rewriting for bundling resources
|
|
636
|
+
- Performance optimization for large projects
|
|
637
|
+
- Integration with tiktoken for accurate token counting
|
|
638
|
+
|
|
639
|
+
## Documentation
|
|
640
|
+
|
|
641
|
+
- [Project Documentation](../../docs)
|
|
642
|
+
- [Architecture](../../docs/architecture/README.md)
|
|
643
|
+
|
|
644
|
+
## License
|
|
645
|
+
|
|
646
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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 { ResourceRegistry, type CrawlOptions, type ResourceRegistryOptions, type RegistryStats, } from './resource-registry.js';
|
|
29
|
+
export type { LinkType, HeadingNode, ResourceLink, ResourceMetadata, ValidationSeverity, ValidationIssue, ValidationResult, } from './types.js';
|
|
30
|
+
export { LinkTypeSchema, HeadingNodeSchema, ResourceLinkSchema, ResourceMetadataSchema, } from './schemas/resource-metadata.js';
|
|
31
|
+
export { ValidationSeveritySchema, ValidationIssueSchema, ValidationResultSchema, } from './schemas/validation-result.js';
|
|
32
|
+
export { parseMarkdown, type ParseResult } from './link-parser.js';
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EACL,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,aAAa,GACnB,MAAM,wBAAwB,CAAC;AAGhC,YAAY,EACV,QAAQ,EACR,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAGpB,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;AAGxC,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|