doc-freshness-checker 1.0.0
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/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/cache/cacheManager.d.ts +42 -0
- package/dist/cache/cacheManager.js +138 -0
- package/dist/cache/cacheManager.js.map +1 -0
- package/dist/cache/cacheManager.test.d.ts +1 -0
- package/dist/cache/cacheManager.test.js +142 -0
- package/dist/cache/cacheManager.test.js.map +1 -0
- package/dist/cli.d.ts +32 -0
- package/dist/cli.js +137 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +184 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/config/defaults.d.ts +5 -0
- package/dist/config/defaults.js +135 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/defineConfig.d.ts +28 -0
- package/dist/config/defineConfig.js +30 -0
- package/dist/config/defineConfig.js.map +1 -0
- package/dist/config/defineConfig.test.d.ts +1 -0
- package/dist/config/defineConfig.test.js +10 -0
- package/dist/config/defineConfig.test.js.map +1 -0
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.js +250 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/loader.test.d.ts +1 -0
- package/dist/config/loader.test.js +276 -0
- package/dist/config/loader.test.js.map +1 -0
- package/dist/git/changeTracker.d.ts +44 -0
- package/dist/git/changeTracker.js +149 -0
- package/dist/git/changeTracker.js.map +1 -0
- package/dist/git/changeTracker.test.d.ts +1 -0
- package/dist/git/changeTracker.test.js +184 -0
- package/dist/git/changeTracker.test.js.map +1 -0
- package/dist/graph/codeDocGraph.d.ts +43 -0
- package/dist/graph/codeDocGraph.js +103 -0
- package/dist/graph/codeDocGraph.js.map +1 -0
- package/dist/graph/codeDocGraph.test.d.ts +1 -0
- package/dist/graph/codeDocGraph.test.js +78 -0
- package/dist/graph/codeDocGraph.test.js.map +1 -0
- package/dist/graph/graphBuilder.d.ts +17 -0
- package/dist/graph/graphBuilder.js +76 -0
- package/dist/graph/graphBuilder.js.map +1 -0
- package/dist/graph/graphBuilder.test.d.ts +1 -0
- package/dist/graph/graphBuilder.test.js +87 -0
- package/dist/graph/graphBuilder.test.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/documentParser.d.ts +22 -0
- package/dist/parsers/documentParser.js +76 -0
- package/dist/parsers/documentParser.js.map +1 -0
- package/dist/parsers/documentParser.test.d.ts +1 -0
- package/dist/parsers/documentParser.test.js +116 -0
- package/dist/parsers/documentParser.test.js.map +1 -0
- package/dist/parsers/extractors/baseExtractor.d.ts +19 -0
- package/dist/parsers/extractors/baseExtractor.js +33 -0
- package/dist/parsers/extractors/baseExtractor.js.map +1 -0
- package/dist/parsers/extractors/baseExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/baseExtractor.test.js +43 -0
- package/dist/parsers/extractors/baseExtractor.test.js.map +1 -0
- package/dist/parsers/extractors/codePatternExtractor.d.ts +13 -0
- package/dist/parsers/extractors/codePatternExtractor.js +108 -0
- package/dist/parsers/extractors/codePatternExtractor.js.map +1 -0
- package/dist/parsers/extractors/codePatternExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/codePatternExtractor.test.js +49 -0
- package/dist/parsers/extractors/codePatternExtractor.test.js.map +1 -0
- package/dist/parsers/extractors/dependencyExtractor.d.ts +12 -0
- package/dist/parsers/extractors/dependencyExtractor.js +92 -0
- package/dist/parsers/extractors/dependencyExtractor.js.map +1 -0
- package/dist/parsers/extractors/dependencyExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/dependencyExtractor.test.js +48 -0
- package/dist/parsers/extractors/dependencyExtractor.test.js.map +1 -0
- package/dist/parsers/extractors/directoryStructureExtractor.d.ts +34 -0
- package/dist/parsers/extractors/directoryStructureExtractor.js +168 -0
- package/dist/parsers/extractors/directoryStructureExtractor.js.map +1 -0
- package/dist/parsers/extractors/directoryStructureExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/directoryStructureExtractor.test.js +121 -0
- package/dist/parsers/extractors/directoryStructureExtractor.test.js.map +1 -0
- package/dist/parsers/extractors/externalUrlExtractor.d.ts +14 -0
- package/dist/parsers/extractors/externalUrlExtractor.js +53 -0
- package/dist/parsers/extractors/externalUrlExtractor.js.map +1 -0
- package/dist/parsers/extractors/externalUrlExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/externalUrlExtractor.test.js +85 -0
- package/dist/parsers/extractors/externalUrlExtractor.test.js.map +1 -0
- package/dist/parsers/extractors/filePathExtractor.d.ts +18 -0
- package/dist/parsers/extractors/filePathExtractor.js +72 -0
- package/dist/parsers/extractors/filePathExtractor.js.map +1 -0
- package/dist/parsers/extractors/filePathExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/filePathExtractor.test.js +73 -0
- package/dist/parsers/extractors/filePathExtractor.test.js.map +1 -0
- package/dist/parsers/extractors/versionExtractor.d.ts +11 -0
- package/dist/parsers/extractors/versionExtractor.js +74 -0
- package/dist/parsers/extractors/versionExtractor.js.map +1 -0
- package/dist/parsers/extractors/versionExtractor.test.d.ts +1 -0
- package/dist/parsers/extractors/versionExtractor.test.js +55 -0
- package/dist/parsers/extractors/versionExtractor.test.js.map +1 -0
- package/dist/plugins/plugin.d.ts +32 -0
- package/dist/plugins/plugin.js +40 -0
- package/dist/plugins/plugin.js.map +1 -0
- package/dist/plugins/plugin.test.d.ts +1 -0
- package/dist/plugins/plugin.test.js +23 -0
- package/dist/plugins/plugin.test.js.map +1 -0
- package/dist/reporters/consoleReporter.d.ts +15 -0
- package/dist/reporters/consoleReporter.js +73 -0
- package/dist/reporters/consoleReporter.js.map +1 -0
- package/dist/reporters/consoleReporter.test.d.ts +1 -0
- package/dist/reporters/consoleReporter.test.js +155 -0
- package/dist/reporters/consoleReporter.test.js.map +1 -0
- package/dist/reporters/enhancedReporter.d.ts +12 -0
- package/dist/reporters/enhancedReporter.js +81 -0
- package/dist/reporters/enhancedReporter.js.map +1 -0
- package/dist/reporters/enhancedReporter.test.d.ts +1 -0
- package/dist/reporters/enhancedReporter.test.js +152 -0
- package/dist/reporters/enhancedReporter.test.js.map +1 -0
- package/dist/reporters/jsonReporter.d.ts +11 -0
- package/dist/reporters/jsonReporter.js +20 -0
- package/dist/reporters/jsonReporter.js.map +1 -0
- package/dist/reporters/jsonReporter.test.d.ts +1 -0
- package/dist/reporters/jsonReporter.test.js +31 -0
- package/dist/reporters/jsonReporter.test.js.map +1 -0
- package/dist/reporters/markdownReporter.d.ts +11 -0
- package/dist/reporters/markdownReporter.js +55 -0
- package/dist/reporters/markdownReporter.js.map +1 -0
- package/dist/reporters/markdownReporter.test.d.ts +1 -0
- package/dist/reporters/markdownReporter.test.js +136 -0
- package/dist/reporters/markdownReporter.test.js.map +1 -0
- package/dist/runner.d.ts +9 -0
- package/dist/runner.js +265 -0
- package/dist/runner.js.map +1 -0
- package/dist/runner.test.d.ts +1 -0
- package/dist/runner.test.js +353 -0
- package/dist/runner.test.js.map +1 -0
- package/dist/scoring/freshnessScorer.d.ts +40 -0
- package/dist/scoring/freshnessScorer.js +170 -0
- package/dist/scoring/freshnessScorer.js.map +1 -0
- package/dist/scoring/freshnessScorer.test.d.ts +1 -0
- package/dist/scoring/freshnessScorer.test.js +397 -0
- package/dist/scoring/freshnessScorer.test.js.map +1 -0
- package/dist/semantic/vectorSearch.d.ts +84 -0
- package/dist/semantic/vectorSearch.js +484 -0
- package/dist/semantic/vectorSearch.js.map +1 -0
- package/dist/semantic/vectorSearch.test.d.ts +1 -0
- package/dist/semantic/vectorSearch.test.js +660 -0
- package/dist/semantic/vectorSearch.test.js.map +1 -0
- package/dist/setupTests.d.ts +4 -0
- package/dist/setupTests.js +11 -0
- package/dist/setupTests.js.map +1 -0
- package/dist/test-utils/console.d.ts +2 -0
- package/dist/test-utils/console.js +3 -0
- package/dist/test-utils/console.js.map +1 -0
- package/dist/test-utils/factories.d.ts +3 -0
- package/dist/test-utils/factories.js +25 -0
- package/dist/test-utils/factories.js.map +1 -0
- package/dist/test-utils/tempFiles.d.ts +1 -0
- package/dist/test-utils/tempFiles.js +12 -0
- package/dist/test-utils/tempFiles.js.map +1 -0
- package/dist/types.d.ts +304 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/boundedMap.d.ts +8 -0
- package/dist/utils/boundedMap.js +22 -0
- package/dist/utils/boundedMap.js.map +1 -0
- package/dist/utils/boundedMap.test.d.ts +1 -0
- package/dist/utils/boundedMap.test.js +57 -0
- package/dist/utils/boundedMap.test.js.map +1 -0
- package/dist/utils/illustrativePatterns.d.ts +28 -0
- package/dist/utils/illustrativePatterns.js +80 -0
- package/dist/utils/illustrativePatterns.js.map +1 -0
- package/dist/utils/illustrativePatterns.test.d.ts +1 -0
- package/dist/utils/illustrativePatterns.test.js +48 -0
- package/dist/utils/illustrativePatterns.test.js.map +1 -0
- package/dist/utils/incremental.d.ts +36 -0
- package/dist/utils/incremental.js +87 -0
- package/dist/utils/incremental.js.map +1 -0
- package/dist/utils/incremental.test.d.ts +1 -0
- package/dist/utils/incremental.test.js +84 -0
- package/dist/utils/incremental.test.js.map +1 -0
- package/dist/utils/parallel.d.ts +14 -0
- package/dist/utils/parallel.js +43 -0
- package/dist/utils/parallel.js.map +1 -0
- package/dist/utils/parallel.test.d.ts +1 -0
- package/dist/utils/parallel.test.js +48 -0
- package/dist/utils/parallel.test.js.map +1 -0
- package/dist/utils/pathSecurity.d.ts +12 -0
- package/dist/utils/pathSecurity.js +22 -0
- package/dist/utils/pathSecurity.js.map +1 -0
- package/dist/utils/pathSecurity.test.d.ts +1 -0
- package/dist/utils/pathSecurity.test.js +34 -0
- package/dist/utils/pathSecurity.test.js.map +1 -0
- package/dist/utils/similarity.d.ts +12 -0
- package/dist/utils/similarity.js +64 -0
- package/dist/utils/similarity.js.map +1 -0
- package/dist/utils/similarity.test.d.ts +1 -0
- package/dist/utils/similarity.test.js +49 -0
- package/dist/utils/similarity.test.js.map +1 -0
- package/dist/utils/validation.d.ts +13 -0
- package/dist/utils/validation.js +24 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +28 -0
- package/dist/utils/validation.test.js.map +1 -0
- package/dist/validators/codePatternValidator.d.ts +28 -0
- package/dist/validators/codePatternValidator.js +200 -0
- package/dist/validators/codePatternValidator.js.map +1 -0
- package/dist/validators/codePatternValidator.test.d.ts +1 -0
- package/dist/validators/codePatternValidator.test.js +86 -0
- package/dist/validators/codePatternValidator.test.js.map +1 -0
- package/dist/validators/dependencyValidator.d.ts +12 -0
- package/dist/validators/dependencyValidator.js +102 -0
- package/dist/validators/dependencyValidator.js.map +1 -0
- package/dist/validators/dependencyValidator.test.d.ts +1 -0
- package/dist/validators/dependencyValidator.test.js +179 -0
- package/dist/validators/dependencyValidator.test.js.map +1 -0
- package/dist/validators/directoryValidator.d.ts +30 -0
- package/dist/validators/directoryValidator.js +192 -0
- package/dist/validators/directoryValidator.js.map +1 -0
- package/dist/validators/directoryValidator.test.d.ts +1 -0
- package/dist/validators/directoryValidator.test.js +193 -0
- package/dist/validators/directoryValidator.test.js.map +1 -0
- package/dist/validators/fileValidator.d.ts +16 -0
- package/dist/validators/fileValidator.js +114 -0
- package/dist/validators/fileValidator.js.map +1 -0
- package/dist/validators/fileValidator.test.d.ts +1 -0
- package/dist/validators/fileValidator.test.js +108 -0
- package/dist/validators/fileValidator.test.js.map +1 -0
- package/dist/validators/urlValidator.d.ts +25 -0
- package/dist/validators/urlValidator.js +320 -0
- package/dist/validators/urlValidator.js.map +1 -0
- package/dist/validators/urlValidator.test.d.ts +1 -0
- package/dist/validators/urlValidator.test.js +252 -0
- package/dist/validators/urlValidator.test.js.map +1 -0
- package/dist/validators/validationEngine.d.ts +23 -0
- package/dist/validators/validationEngine.js +117 -0
- package/dist/validators/validationEngine.js.map +1 -0
- package/dist/validators/validationEngine.test.d.ts +1 -0
- package/dist/validators/validationEngine.test.js +82 -0
- package/dist/validators/validationEngine.test.js.map +1 -0
- package/dist/validators/versionValidator.d.ts +18 -0
- package/dist/validators/versionValidator.js +211 -0
- package/dist/validators/versionValidator.js.map +1 -0
- package/dist/validators/versionValidator.test.d.ts +1 -0
- package/dist/validators/versionValidator.test.js +308 -0
- package/dist/validators/versionValidator.test.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized patterns for detecting illustrative/placeholder content in documentation
|
|
3
|
+
*
|
|
4
|
+
* These patterns identify paths, filenames, and code symbols that are commonly used
|
|
5
|
+
* as examples in tutorials and documentation but don't represent actual files/code.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Patterns for detecting illustrative file/directory paths
|
|
9
|
+
* Used by: FilePathExtractor, DirectoryStructureExtractor, FileValidator, DirectoryValidator
|
|
10
|
+
*/
|
|
11
|
+
export const ILLUSTRATIVE_PATH_PATTERNS = [
|
|
12
|
+
// Generic placeholder prefixes (case-insensitive)
|
|
13
|
+
/^(?:Your|My|Example|Sample|Demo|Test|Foo|Bar|Baz|Dummy|Mock|Fake|Stub)/i,
|
|
14
|
+
// Common tutorial placeholders
|
|
15
|
+
/^(?:first|second|third|another|some|new)\./i,
|
|
16
|
+
// Very short generic names without extensions
|
|
17
|
+
/^(?:foo|bar|baz|qux|quux)$/i,
|
|
18
|
+
// Paths containing angle brackets or curly braces (template syntax)
|
|
19
|
+
/<[^>]+>/,
|
|
20
|
+
/\{[^}]+\}/,
|
|
21
|
+
// Paths with obvious placeholder segments
|
|
22
|
+
/\/(?:your|my|example|sample)-/i,
|
|
23
|
+
/\/\[.*\]\//,
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Patterns for detecting illustrative code symbol names
|
|
27
|
+
* Used by: CodePatternExtractor, CodePatternValidator
|
|
28
|
+
*/
|
|
29
|
+
export const ILLUSTRATIVE_SYMBOL_PATTERNS = [
|
|
30
|
+
// Generic placeholder prefixes followed by PascalCase
|
|
31
|
+
/^(?:Your|My|Example|Sample|Demo|Test|Foo|Bar|Baz|Dummy|Mock|Fake|Stub)[A-Z]/i,
|
|
32
|
+
// HTTP methods used as function names in REST tutorials
|
|
33
|
+
/^(?:POST|GET|PUT|DELETE|PATCH)$/,
|
|
34
|
+
// Very short names (1-2 chars) are likely false positives from word parsing
|
|
35
|
+
/^[a-z]{1,2}$/,
|
|
36
|
+
// Common tutorial component/function names
|
|
37
|
+
/^(?:Chat|App|Button|Card|Modal|Form|Input|List|Item|Header|Footer|Sidebar|Nav|Menu|Page|Home|About|Contact|Login|Signup|Profile|Dashboard|Settings)$/,
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Check if a path looks like an illustrative/placeholder path
|
|
41
|
+
*/
|
|
42
|
+
export function isIllustrativePath(itemPath, customPatterns = []) {
|
|
43
|
+
const patterns = [...ILLUSTRATIVE_PATH_PATTERNS, ...customPatterns];
|
|
44
|
+
const segments = itemPath.split('/');
|
|
45
|
+
const filename = segments[segments.length - 1];
|
|
46
|
+
// Check filename against patterns
|
|
47
|
+
for (const pattern of patterns) {
|
|
48
|
+
if (pattern.test(filename)) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Check if any segment in the path looks illustrative
|
|
53
|
+
for (const segment of segments.slice(0, -1)) {
|
|
54
|
+
for (const pattern of patterns) {
|
|
55
|
+
if (pattern.test(segment)) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if a code symbol name looks like an illustrative/placeholder name
|
|
64
|
+
*/
|
|
65
|
+
export function isIllustrativeSymbol(name, customPatterns = []) {
|
|
66
|
+
const patterns = [...ILLUSTRATIVE_SYMBOL_PATTERNS, ...customPatterns];
|
|
67
|
+
for (const pattern of patterns) {
|
|
68
|
+
if (pattern.test(name)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Convert string patterns from config to RegExp objects
|
|
76
|
+
*/
|
|
77
|
+
export function compilePatterns(patterns) {
|
|
78
|
+
return patterns.map((p) => new RegExp(p, 'i'));
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=illustrativePatterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"illustrativePatterns.js","sourceRoot":"","sources":["../../src/utils/illustrativePatterns.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAa;IAClD,kDAAkD;IAClD,yEAAyE;IACzE,+BAA+B;IAC/B,6CAA6C;IAC7C,8CAA8C;IAC9C,6BAA6B;IAC7B,oEAAoE;IACpE,SAAS;IACT,WAAW;IACX,0CAA0C;IAC1C,gCAAgC;IAChC,YAAY;CACb,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAa;IACpD,sDAAsD;IACtD,8EAA8E;IAC9E,wDAAwD;IACxD,iCAAiC;IACjC,4EAA4E;IAC5E,cAAc;IACd,2CAA2C;IAC3C,sJAAsJ;CACvJ,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,iBAA2B,EAAE;IAChF,MAAM,QAAQ,GAAG,CAAC,GAAG,0BAA0B,EAAE,GAAG,cAAc,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE/C,kCAAkC;IAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,iBAA2B,EAAE;IAC9E,MAAM,QAAQ,GAAG,CAAC,GAAG,4BAA4B,EAAE,GAAG,cAAc,CAAC,CAAC;IAEtE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,QAAkB;IAChD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isIllustrativePath, isIllustrativeSymbol, compilePatterns, ILLUSTRATIVE_PATH_PATTERNS, ILLUSTRATIVE_SYMBOL_PATTERNS, } from './illustrativePatterns.js';
|
|
2
|
+
describe('compilePatterns', () => {
|
|
3
|
+
it('converts string patterns to case-insensitive RegExp', () => {
|
|
4
|
+
const patterns = compilePatterns(['^test', 'example$']);
|
|
5
|
+
expect(patterns).toHaveLength(2);
|
|
6
|
+
expect(patterns[0].test('TestFile')).toBe(true);
|
|
7
|
+
expect(patterns[1].test('myExample')).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
describe('isIllustrativePath', () => {
|
|
11
|
+
it.each([
|
|
12
|
+
'YourProject/file.ts',
|
|
13
|
+
'my-project/src/index.ts',
|
|
14
|
+
'Example.tsx',
|
|
15
|
+
'sample-app/main.py',
|
|
16
|
+
'foo',
|
|
17
|
+
'src/<component>/file.ts',
|
|
18
|
+
'src/{name}/file.ts',
|
|
19
|
+
'first.ts',
|
|
20
|
+
])('detects illustrative path: %s', (p) => {
|
|
21
|
+
expect(isIllustrativePath(p)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it.each(['src/utils/helper.ts', 'package.json', 'tsconfig.json'])('does not flag real path: %s', (p) => {
|
|
24
|
+
expect(isIllustrativePath(p)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
it('supports custom patterns', () => {
|
|
27
|
+
const custom = [/^custom-placeholder/i];
|
|
28
|
+
expect(isIllustrativePath('custom-placeholder.ts', custom)).toBe(true);
|
|
29
|
+
expect(isIllustrativePath('real-file.ts', custom)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('isIllustrativeSymbol', () => {
|
|
33
|
+
it.each(['YourComponent', 'ExampleService', 'FooBar', 'MockAdapter', 'POST', 'a', 'Chat', 'Dashboard'])('detects illustrative symbol: %s', (s) => {
|
|
34
|
+
expect(isIllustrativeSymbol(s)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it.each(['DocumentParser', 'ValidationEngine', 'runParallel'])('does not flag real symbol: %s', (s) => {
|
|
37
|
+
expect(isIllustrativeSymbol(s)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('pattern arrays are non-empty', () => {
|
|
41
|
+
it('ILLUSTRATIVE_PATH_PATTERNS has entries', () => {
|
|
42
|
+
expect(ILLUSTRATIVE_PATH_PATTERNS.length).toBeGreaterThan(0);
|
|
43
|
+
});
|
|
44
|
+
it('ILLUSTRATIVE_SYMBOL_PATTERNS has entries', () => {
|
|
45
|
+
expect(ILLUSTRATIVE_SYMBOL_PATTERNS.length).toBeGreaterThan(0);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=illustrativePatterns.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"illustrativePatterns.test.js","sourceRoot":"","sources":["../../src/utils/illustrativePatterns.test.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AAEnC,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,QAAQ,GAAG,eAAe,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;QACxD,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,IAAI,CAAC;QACN,qBAAqB;QACrB,yBAAyB;QACzB,aAAa;QACb,oBAAoB;QACpB,KAAK;QACL,yBAAyB;QACzB,oBAAoB;QACpB,UAAU;KACX,CAAC,CAAC,+BAA+B,EAAE,CAAC,CAAC,EAAE,EAAE;QACxC,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC,CAAC,qBAAqB,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC,6BAA6B,EAAE,CAAC,CAAC,EAAE,EAAE;QACrG,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,MAAM,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACxC,MAAM,CAAC,kBAAkB,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE,MAAM,CAAC,kBAAkB,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,gBAAgB,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CACrG,iCAAiC,EACjC,CAAC,CAAC,EAAE,EAAE;QACJ,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC,CACF,CAAC;IAEF,EAAE,CAAC,IAAI,CAAC,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAC,CAAC,+BAA+B,EAAE,CAAC,CAAC,EAAE,EAAE;QACpG,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC5C,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,4BAA4B,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Document, IncrementalStats } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Incremental checker for skipping unchanged documentation files
|
|
4
|
+
* Uses file hashes to detect changes between runs
|
|
5
|
+
*/
|
|
6
|
+
export declare class IncrementalChecker {
|
|
7
|
+
private stateDir;
|
|
8
|
+
private stateFile;
|
|
9
|
+
private previousHashes;
|
|
10
|
+
private currentHashes;
|
|
11
|
+
constructor(stateDir?: string);
|
|
12
|
+
/**
|
|
13
|
+
* Load previous state from cache
|
|
14
|
+
*/
|
|
15
|
+
loadState(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Save current state to cache
|
|
18
|
+
*/
|
|
19
|
+
saveState(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Get MD5 hash of a file
|
|
22
|
+
*/
|
|
23
|
+
getHash(filePath: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if a file has changed since last run
|
|
26
|
+
*/
|
|
27
|
+
shouldCheck(filePath: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Filter documents to only include changed ones
|
|
30
|
+
*/
|
|
31
|
+
filterChanged(documents: Document[]): Promise<Document[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Get statistics about the incremental check
|
|
34
|
+
*/
|
|
35
|
+
getStats(totalDocs: number, changedDocs: number): IncrementalStats;
|
|
36
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
/**
|
|
5
|
+
* Incremental checker for skipping unchanged documentation files
|
|
6
|
+
* Uses file hashes to detect changes between runs
|
|
7
|
+
*/
|
|
8
|
+
export class IncrementalChecker {
|
|
9
|
+
stateDir;
|
|
10
|
+
stateFile;
|
|
11
|
+
previousHashes;
|
|
12
|
+
currentHashes;
|
|
13
|
+
constructor(stateDir = '.doc-freshness-cache') {
|
|
14
|
+
this.stateDir = stateDir;
|
|
15
|
+
this.stateFile = path.join(stateDir, 'file-hashes.json');
|
|
16
|
+
this.previousHashes = new Map();
|
|
17
|
+
this.currentHashes = new Map();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load previous state from cache
|
|
21
|
+
*/
|
|
22
|
+
async loadState() {
|
|
23
|
+
try {
|
|
24
|
+
const data = await fs.promises.readFile(this.stateFile, 'utf-8');
|
|
25
|
+
const parsed = JSON.parse(data);
|
|
26
|
+
this.previousHashes = new Map(Object.entries(parsed));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// No previous state
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Save current state to cache
|
|
34
|
+
*/
|
|
35
|
+
async saveState() {
|
|
36
|
+
await fs.promises.mkdir(this.stateDir, { recursive: true });
|
|
37
|
+
const data = Object.fromEntries(this.currentHashes);
|
|
38
|
+
await fs.promises.writeFile(this.stateFile, JSON.stringify(data, null, 2));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get MD5 hash of a file
|
|
42
|
+
*/
|
|
43
|
+
async getHash(filePath) {
|
|
44
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
45
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if a file has changed since last run
|
|
49
|
+
*/
|
|
50
|
+
async shouldCheck(filePath) {
|
|
51
|
+
try {
|
|
52
|
+
const hash = await this.getHash(filePath);
|
|
53
|
+
this.currentHashes.set(filePath, hash);
|
|
54
|
+
const previousHash = this.previousHashes.get(filePath);
|
|
55
|
+
return hash !== previousHash;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// File read error - should check
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Filter documents to only include changed ones
|
|
64
|
+
*/
|
|
65
|
+
async filterChanged(documents) {
|
|
66
|
+
await this.loadState();
|
|
67
|
+
const changed = [];
|
|
68
|
+
for (const doc of documents) {
|
|
69
|
+
if (await this.shouldCheck(doc.absolutePath)) {
|
|
70
|
+
changed.push(doc);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return changed;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get statistics about the incremental check
|
|
77
|
+
*/
|
|
78
|
+
getStats(totalDocs, changedDocs) {
|
|
79
|
+
return {
|
|
80
|
+
total: totalDocs,
|
|
81
|
+
changed: changedDocs,
|
|
82
|
+
skipped: totalDocs - changedDocs,
|
|
83
|
+
percentSkipped: totalDocs > 0 ? Math.round(((totalDocs - changedDocs) / totalDocs) * 100) : 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=incremental.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental.js","sourceRoot":"","sources":["../../src/utils/incremental.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import { IncrementalChecker } from './incremental.js';
|
|
4
|
+
function makeDoc(docPath) {
|
|
5
|
+
return {
|
|
6
|
+
path: docPath,
|
|
7
|
+
absolutePath: `/project/${docPath}`,
|
|
8
|
+
content: 'content',
|
|
9
|
+
format: 'markdown',
|
|
10
|
+
lines: ['content'],
|
|
11
|
+
references: [],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
describe('IncrementalChecker', () => {
|
|
15
|
+
let checker;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
checker = new IncrementalChecker('/tmp/test-cache');
|
|
18
|
+
});
|
|
19
|
+
describe('getStats', () => {
|
|
20
|
+
it.each([
|
|
21
|
+
{ total: 10, changed: 3, skipped: 7, pct: 70 },
|
|
22
|
+
{ total: 0, changed: 0, skipped: 0, pct: 0 },
|
|
23
|
+
{ total: 5, changed: 5, skipped: 0, pct: 0 },
|
|
24
|
+
])('computes stats for total=$total changed=$changed', ({ total, changed, skipped, pct }) => {
|
|
25
|
+
const stats = checker.getStats(total, changed);
|
|
26
|
+
expect(stats).toEqual({ total, changed, skipped, percentSkipped: pct });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('loadState', () => {
|
|
30
|
+
it('loads previous hashes from disk', async () => {
|
|
31
|
+
vi.spyOn(fs.promises, 'readFile').mockResolvedValue(JSON.stringify({ 'file.md': 'abc123' }));
|
|
32
|
+
await checker.loadState();
|
|
33
|
+
});
|
|
34
|
+
it('handles missing state file gracefully', async () => {
|
|
35
|
+
vi.spyOn(fs.promises, 'readFile').mockRejectedValue(new Error('ENOENT'));
|
|
36
|
+
await expect(checker.loadState()).resolves.not.toThrow();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('saveState', () => {
|
|
40
|
+
it('writes current hashes to disk', async () => {
|
|
41
|
+
const mkdirSpy = vi.spyOn(fs.promises, 'mkdir').mockResolvedValue(undefined);
|
|
42
|
+
const writeSpy = vi.spyOn(fs.promises, 'writeFile').mockResolvedValue(undefined);
|
|
43
|
+
await checker.saveState();
|
|
44
|
+
expect(mkdirSpy).toHaveBeenCalledWith('/tmp/test-cache', { recursive: true });
|
|
45
|
+
expect(writeSpy).toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('shouldCheck', () => {
|
|
49
|
+
it('returns true for new files not in previous state', async () => {
|
|
50
|
+
vi.spyOn(fs.promises, 'readFile').mockResolvedValueOnce(JSON.stringify({})).mockResolvedValueOnce('file content');
|
|
51
|
+
await checker.loadState();
|
|
52
|
+
expect(await checker.shouldCheck('/project/new.md')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
it('returns false for unchanged files', async () => {
|
|
55
|
+
const content = 'file content';
|
|
56
|
+
const expectedHash = crypto.createHash('md5').update(content).digest('hex');
|
|
57
|
+
vi.spyOn(fs.promises, 'readFile')
|
|
58
|
+
.mockResolvedValueOnce(JSON.stringify({ '/project/same.md': expectedHash }))
|
|
59
|
+
.mockResolvedValueOnce(content);
|
|
60
|
+
await checker.loadState();
|
|
61
|
+
expect(await checker.shouldCheck('/project/same.md')).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
it('returns true when file read fails', async () => {
|
|
64
|
+
vi.spyOn(fs.promises, 'readFile').mockResolvedValueOnce(JSON.stringify({})).mockRejectedValueOnce(new Error('read error'));
|
|
65
|
+
await checker.loadState();
|
|
66
|
+
expect(await checker.shouldCheck('/project/broken.md')).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('filterChanged', () => {
|
|
70
|
+
it('returns only documents whose content has changed', async () => {
|
|
71
|
+
const content = 'unchanged';
|
|
72
|
+
const hash = crypto.createHash('md5').update(content).digest('hex');
|
|
73
|
+
vi.spyOn(fs.promises, 'readFile')
|
|
74
|
+
.mockResolvedValueOnce(JSON.stringify({ '/project/unchanged.md': hash }))
|
|
75
|
+
.mockResolvedValueOnce(content)
|
|
76
|
+
.mockResolvedValueOnce('new content');
|
|
77
|
+
const docs = [makeDoc('unchanged.md'), makeDoc('changed.md')];
|
|
78
|
+
const changed = await checker.filterChanged(docs);
|
|
79
|
+
expect(changed).toHaveLength(1);
|
|
80
|
+
expect(changed[0].path).toBe('changed.md');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
//# sourceMappingURL=incremental.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental.test.js","sourceRoot":"","sources":["../../src/utils/incremental.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run tasks in parallel with concurrency limit.
|
|
3
|
+
* Individual task failures don't abort remaining tasks --
|
|
4
|
+
* all rejections are collected and re-thrown via Promise.allSettled + Promise.all.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runParallel<T>(tasks: Array<() => Promise<T>>, concurrency?: number): Promise<T[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Batch an array into chunks
|
|
9
|
+
*/
|
|
10
|
+
export declare function batch<T>(array: T[], size: number): T[][];
|
|
11
|
+
/**
|
|
12
|
+
* Delay execution
|
|
13
|
+
*/
|
|
14
|
+
export declare function delay(ms: number): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run tasks in parallel with concurrency limit.
|
|
3
|
+
* Individual task failures don't abort remaining tasks --
|
|
4
|
+
* all rejections are collected and re-thrown via Promise.allSettled + Promise.all.
|
|
5
|
+
*/
|
|
6
|
+
export async function runParallel(tasks, concurrency = 10) {
|
|
7
|
+
const results = [];
|
|
8
|
+
const executing = new Set();
|
|
9
|
+
for (const task of tasks) {
|
|
10
|
+
const promise = task();
|
|
11
|
+
results.push(promise);
|
|
12
|
+
const cleanup = promise
|
|
13
|
+
.then(() => { }, () => { })
|
|
14
|
+
.then(() => {
|
|
15
|
+
executing.delete(cleanup);
|
|
16
|
+
});
|
|
17
|
+
executing.add(cleanup);
|
|
18
|
+
if (executing.size >= concurrency) {
|
|
19
|
+
await Promise.race(executing);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return Promise.all(results);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Batch an array into chunks
|
|
26
|
+
*/
|
|
27
|
+
export function batch(array, size) {
|
|
28
|
+
if (size <= 0) {
|
|
29
|
+
throw new Error('Batch size must be greater than 0');
|
|
30
|
+
}
|
|
31
|
+
const batches = [];
|
|
32
|
+
for (let i = 0; i < array.length; i += size) {
|
|
33
|
+
batches.push(array.slice(i, i + size));
|
|
34
|
+
}
|
|
35
|
+
return batches;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Delay execution
|
|
39
|
+
*/
|
|
40
|
+
export function delay(ms) {
|
|
41
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=parallel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel.js","sourceRoot":"","sources":["../../src/utils/parallel.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAI,KAA8B,EAAE,cAAsB,EAAE;IAC3F,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,SAAS,GAAuB,IAAI,GAAG,EAAE,CAAC;IAEhD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEtB,MAAM,OAAO,GAAG,OAAO;aACpB,IAAI,CACH,GAAG,EAAE,GAAE,CAAC,EACR,GAAG,EAAE,GAAE,CAAC,CACT;aACA,IAAI,CAAC,GAAG,EAAE;YACT,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACL,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAEvB,IAAI,SAAS,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;YAClC,MAAM,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,KAAK,CAAI,KAAU,EAAE,IAAY;IAC/C,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,OAAO,GAAU,EAAE,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { runParallel, batch, delay } from './parallel.js';
|
|
2
|
+
describe('batch', () => {
|
|
3
|
+
it.each([
|
|
4
|
+
{ input: [1, 2, 3, 4, 5], size: 2, expected: [[1, 2], [3, 4], [5]] },
|
|
5
|
+
{ input: [1, 2, 3], size: 5, expected: [[1, 2, 3]] },
|
|
6
|
+
{ input: [], size: 3, expected: [] },
|
|
7
|
+
])('splits array of $input.length into chunks of $size', ({ input, size, expected }) => {
|
|
8
|
+
expect(batch(input, size)).toEqual(expected);
|
|
9
|
+
});
|
|
10
|
+
it('throws when batch size is non-positive', () => {
|
|
11
|
+
expect(() => batch([1, 2, 3], 0)).toThrow('Batch size must be greater than 0');
|
|
12
|
+
expect(() => batch([1, 2, 3], -1)).toThrow('Batch size must be greater than 0');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('delay', () => {
|
|
16
|
+
it('resolves after the specified time', async () => {
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
await delay(50);
|
|
19
|
+
expect(Date.now() - start).toBeGreaterThanOrEqual(40);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe('runParallel', () => {
|
|
23
|
+
it('runs all tasks and returns results in order', async () => {
|
|
24
|
+
const tasks = [() => Promise.resolve(1), () => Promise.resolve(2), () => Promise.resolve(3)];
|
|
25
|
+
const results = await runParallel(tasks);
|
|
26
|
+
expect(results).toEqual([1, 2, 3]);
|
|
27
|
+
});
|
|
28
|
+
it('respects concurrency limit', async () => {
|
|
29
|
+
let concurrent = 0;
|
|
30
|
+
let maxConcurrent = 0;
|
|
31
|
+
const makeTask = (val) => async () => {
|
|
32
|
+
concurrent++;
|
|
33
|
+
maxConcurrent = Math.max(maxConcurrent, concurrent);
|
|
34
|
+
await delay(20);
|
|
35
|
+
concurrent--;
|
|
36
|
+
return val;
|
|
37
|
+
};
|
|
38
|
+
const tasks = Array.from({ length: 6 }, (_, i) => makeTask(i));
|
|
39
|
+
const results = await runParallel(tasks, 2);
|
|
40
|
+
expect(results).toEqual([0, 1, 2, 3, 4, 5]);
|
|
41
|
+
expect(maxConcurrent).toBeLessThanOrEqual(2);
|
|
42
|
+
});
|
|
43
|
+
it('propagates errors from tasks', async () => {
|
|
44
|
+
const tasks = [() => Promise.resolve(1), () => Promise.reject(new Error('fail'))];
|
|
45
|
+
await expect(runParallel(tasks)).rejects.toThrow('fail');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=parallel.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel.test.js","sourceRoot":"","sources":["../../src/utils/parallel.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if candidatePath is equal to or nested within rootDir.
|
|
3
|
+
*/
|
|
4
|
+
export declare function isWithinRoot(candidatePath: string, rootDir: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve project root from config or process cwd.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveProjectRoot(configRootDir?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a document directory relative to project root.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveDocumentDir(rootDir: string, documentPath: string): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Returns true if candidatePath is equal to or nested within rootDir.
|
|
4
|
+
*/
|
|
5
|
+
export function isWithinRoot(candidatePath, rootDir) {
|
|
6
|
+
const resolvedRoot = path.resolve(rootDir);
|
|
7
|
+
const resolvedCandidate = path.resolve(candidatePath);
|
|
8
|
+
return resolvedCandidate === resolvedRoot || resolvedCandidate.startsWith(resolvedRoot + path.sep);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolve project root from config or process cwd.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveProjectRoot(configRootDir) {
|
|
14
|
+
return path.resolve(configRootDir || process.cwd());
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a document directory relative to project root.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveDocumentDir(rootDir, documentPath) {
|
|
20
|
+
return path.dirname(path.resolve(rootDir, documentPath));
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=pathSecurity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathSecurity.js","sourceRoot":"","sources":["../../src/utils/pathSecurity.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,aAAqB,EAAE,OAAe;IACjE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACtD,OAAO,iBAAiB,KAAK,YAAY,IAAI,iBAAiB,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;AACrG,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAsB;IACvD,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,YAAoB;IACtE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { isWithinRoot, resolveProjectRoot, resolveDocumentDir } from './pathSecurity.js';
|
|
3
|
+
describe('isWithinRoot', () => {
|
|
4
|
+
const root = '/project';
|
|
5
|
+
it.each([
|
|
6
|
+
['/project/src/file.ts', true],
|
|
7
|
+
['/project', true],
|
|
8
|
+
['/project/deep/nested/file.ts', true],
|
|
9
|
+
['/other/place', false],
|
|
10
|
+
['/projectExtra/file.ts', false],
|
|
11
|
+
])('isWithinRoot(%s, /project) => %s', (candidate, expected) => {
|
|
12
|
+
expect(isWithinRoot(candidate, root)).toBe(expected);
|
|
13
|
+
});
|
|
14
|
+
it('resolves relative paths before comparing', () => {
|
|
15
|
+
expect(isWithinRoot('/project/src/../src/file.ts', root)).toBe(true);
|
|
16
|
+
expect(isWithinRoot('/project/../other', root)).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('resolveProjectRoot', () => {
|
|
20
|
+
it('returns resolved configRootDir when provided', () => {
|
|
21
|
+
expect(resolveProjectRoot('/my/project')).toBe(path.resolve('/my/project'));
|
|
22
|
+
});
|
|
23
|
+
it('falls back to process.cwd() when no arg', () => {
|
|
24
|
+
expect(resolveProjectRoot()).toBe(path.resolve(process.cwd()));
|
|
25
|
+
expect(resolveProjectRoot(undefined)).toBe(path.resolve(process.cwd()));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('resolveDocumentDir', () => {
|
|
29
|
+
it('returns the directory of the document relative to rootDir', () => {
|
|
30
|
+
const result = resolveDocumentDir('/project', 'docs/guide/README.md');
|
|
31
|
+
expect(result).toBe(path.resolve('/project', 'docs/guide'));
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=pathSecurity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathSecurity.test.js","sourceRoot":"","sources":["../../src/utils/pathSecurity.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEzF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,MAAM,IAAI,GAAG,UAAU,CAAC;IAExB,EAAE,CAAC,IAAI,CAAC;QACN,CAAC,sBAAsB,EAAE,IAAI,CAAC;QAC9B,CAAC,UAAU,EAAE,IAAI,CAAC;QAClB,CAAC,8BAA8B,EAAE,IAAI,CAAC;QACtC,CAAC,cAAc,EAAE,KAAK,CAAC;QACvB,CAAC,uBAAuB,EAAE,KAAK,CAAC;KACjC,CAAC,CAAC,kCAAkC,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;QAC7D,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,YAAY,CAAC,6BAA6B,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,YAAY,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC/D,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,MAAM,GAAG,kBAAkB,CAAC,UAAU,EAAE,sBAAsB,CAAC,CAAC;QACtE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the most similar string from a list using Levenshtein distance
|
|
3
|
+
*/
|
|
4
|
+
export declare function findSimilar(target: string, candidates: string[], maxDistance?: number): string | null;
|
|
5
|
+
/**
|
|
6
|
+
* Calculate Levenshtein distance between two strings
|
|
7
|
+
*/
|
|
8
|
+
export declare function levenshteinDistance(a: string, b: string): number;
|
|
9
|
+
/**
|
|
10
|
+
* Calculate similarity ratio (0-1) between two strings
|
|
11
|
+
*/
|
|
12
|
+
export declare function similarityRatio(a: string, b: string): number;
|