@skillsmith/core 0.5.2 → 0.5.4
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/CHANGELOG.md +14 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/activation/ActivationManager.d.ts +7 -0
- package/dist/src/activation/ActivationManager.d.ts.map +1 -1
- package/dist/src/activation/ActivationManager.js +13 -4
- package/dist/src/activation/ActivationManager.js.map +1 -1
- package/dist/src/analysis/adapters/python.d.ts +16 -11
- package/dist/src/analysis/adapters/python.d.ts.map +1 -1
- package/dist/src/analysis/adapters/python.js +46 -61
- package/dist/src/analysis/adapters/python.js.map +1 -1
- package/dist/src/analysis/router.test.d.ts +2 -0
- package/dist/src/analysis/router.test.d.ts.map +1 -0
- package/dist/src/analysis/router.test.js +411 -0
- package/dist/src/analysis/router.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -1
- package/dist/src/analysis/tree-sitter/manager.js +12 -5
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -1
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts +45 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js +264 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts +12 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js +74 -0
- package/dist/src/analysis/tree-sitter/pythonExtractor.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts +93 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts +22 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js +229 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.hardening.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js +287 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.js.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts +17 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js +142 -0
- package/dist/src/analysis/tree-sitter/pythonIncremental.test.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts +43 -0
- package/dist/src/analysis/tree-sitter/queries/python.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queries/python.js +88 -0
- package/dist/src/analysis/tree-sitter/queries/python.js.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts +13 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js +174 -0
- package/dist/src/analysis/tree-sitter/queryExtractionMatchesOrExceedsRegex.test.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts +11 -0
- package/dist/src/analytics/ROIDashboardService.csv.d.ts.map +1 -0
- package/dist/src/analytics/ROIDashboardService.csv.js +43 -0
- package/dist/src/analytics/ROIDashboardService.csv.js.map +1 -0
- package/dist/src/analytics/ROIDashboardService.d.ts +64 -3
- package/dist/src/analytics/ROIDashboardService.d.ts.map +1 -1
- package/dist/src/analytics/ROIDashboardService.js +116 -45
- package/dist/src/analytics/ROIDashboardService.js.map +1 -1
- package/dist/src/api/schemas.d.ts +19 -4
- package/dist/src/api/schemas.d.ts.map +1 -1
- package/dist/src/api/schemas.js +8 -0
- package/dist/src/api/schemas.js.map +1 -1
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts +18 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.d.ts.map +1 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js +121 -0
- package/dist/src/benchmarks/incrementalParseBenchmark.js.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts +2 -0
- package/dist/src/billing/GDPRComplianceService.test.d.ts.map +1 -0
- package/dist/src/billing/GDPRComplianceService.test.js +405 -0
- package/dist/src/billing/GDPRComplianceService.test.js.map +1 -0
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/SkillParser.coverage.test.d.ts +10 -0
- package/dist/src/indexer/SkillParser.coverage.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.coverage.test.js +76 -0
- package/dist/src/indexer/SkillParser.coverage.test.js.map +1 -0
- package/dist/src/indexer/SkillParser.test.d.ts +2 -0
- package/dist/src/indexer/SkillParser.test.d.ts.map +1 -0
- package/dist/src/indexer/SkillParser.test.js +375 -0
- package/dist/src/indexer/SkillParser.test.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.d.ts +104 -10
- package/dist/src/sources/LocalFilesystemAdapter.d.ts.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts +92 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js +157 -0
- package/dist/src/sources/LocalFilesystemAdapter.helpers.js.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.js +218 -159
- package/dist/src/sources/LocalFilesystemAdapter.js.map +1 -1
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts +78 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.d.ts.map +1 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js +118 -0
- package/dist/src/sources/LocalFilesystemAdapter.scan.js.map +1 -0
- package/dist/src/sources/index.d.ts +1 -1
- package/dist/src/sources/index.d.ts.map +1 -1
- package/dist/src/sources/index.js.map +1 -1
- package/dist/src/sources/types.d.ts +28 -0
- package/dist/src/sources/types.d.ts.map +1 -1
- package/dist/src/telemetry/tracer-imports.d.ts +13 -0
- package/dist/src/telemetry/tracer-imports.d.ts.map +1 -0
- package/dist/src/telemetry/tracer-imports.js +26 -0
- package/dist/src/telemetry/tracer-imports.js.map +1 -0
- package/dist/src/telemetry/tracer.d.ts.map +1 -1
- package/dist/src/telemetry/tracer.js +18 -21
- package/dist/src/telemetry/tracer.js.map +1 -1
- package/dist/src/utils/rate-limit.d.ts +39 -0
- package/dist/src/utils/rate-limit.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.js +48 -0
- package/dist/src/utils/rate-limit.js.map +1 -0
- package/dist/src/utils/rate-limit.test.d.ts +11 -0
- package/dist/src/utils/rate-limit.test.d.ts.map +1 -0
- package/dist/src/utils/rate-limit.test.js +86 -0
- package/dist/src/utils/rate-limit.test.js.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts +178 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.d.ts.map +1 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js +196 -0
- package/dist/src/webhooks/WebhookDeadLetterRepository.js.map +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts +1 -0
- package/dist/src/webhooks/WebhookQueue.d.ts.map +1 -1
- package/dist/src/webhooks/WebhookQueue.js +19 -0
- package/dist/src/webhooks/WebhookQueue.js.map +1 -1
- package/dist/src/webhooks/WebhookQueue.types.d.ts +11 -0
- package/dist/src/webhooks/WebhookQueue.types.d.ts.map +1 -1
- package/dist/src/webhooks/index.d.ts +1 -0
- package/dist/src/webhooks/index.d.ts.map +1 -1
- package/dist/src/webhooks/index.js +2 -0
- package/dist/src/webhooks/index.js.map +1 -1
- package/dist/tests/ActivationManager.test.d.ts +13 -0
- package/dist/tests/ActivationManager.test.d.ts.map +1 -0
- package/dist/tests/ActivationManager.test.js +218 -0
- package/dist/tests/ActivationManager.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts +13 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js +314 -0
- package/dist/tests/LocalFilesystemAdapter.coverage.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts +18 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js +344 -0
- package/dist/tests/LocalFilesystemAdapter.security.test.js.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts +12 -0
- package/dist/tests/LocalFilesystemAdapter.test.d.ts.map +1 -0
- package/dist/tests/LocalFilesystemAdapter.test.js +301 -0
- package/dist/tests/LocalFilesystemAdapter.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts +9 -0
- package/dist/tests/ROIDashboardService.coverage.test.d.ts.map +1 -0
- package/dist/tests/ROIDashboardService.coverage.test.js +118 -0
- package/dist/tests/ROIDashboardService.coverage.test.js.map +1 -0
- package/dist/tests/ROIDashboardService.test.js +87 -0
- package/dist/tests/ROIDashboardService.test.js.map +1 -1
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts +14 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.d.ts.map +1 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js +169 -0
- package/dist/tests/ScraperAdapters.gitlab-coverage.test.js.map +1 -0
- package/dist/tests/ScraperAdapters.test.d.ts +5 -1
- package/dist/tests/ScraperAdapters.test.d.ts.map +1 -1
- package/dist/tests/ScraperAdapters.test.js +6 -336
- package/dist/tests/ScraperAdapters.test.js.map +1 -1
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts +2 -0
- package/dist/tests/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js +333 -0
- package/dist/tests/WebhookDeadLetterRepository.test.js.map +1 -0
- package/dist/tests/WebhookHandler.test.js +93 -1
- package/dist/tests/WebhookHandler.test.js.map +1 -1
- package/dist/tests/WebhookQueue.coverage.test.d.ts +19 -0
- package/dist/tests/WebhookQueue.coverage.test.d.ts.map +1 -0
- package/dist/tests/WebhookQueue.coverage.test.js +190 -0
- package/dist/tests/WebhookQueue.coverage.test.js.map +1 -0
- package/dist/tests/api/client.validation.test.js +37 -0
- package/dist/tests/api/client.validation.test.js.map +1 -1
- package/dist/tests/billing/GDPRCompliance.test.d.ts +2 -2
- package/dist/tests/billing/GDPRCompliance.test.js +221 -36
- package/dist/tests/billing/GDPRCompliance.test.js.map +1 -1
- package/dist/tests/telemetry.test.js +126 -0
- package/dist/tests/telemetry.test.js.map +1 -1
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts +10 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.d.ts.map +1 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js +109 -0
- package/dist/tests/webhooks/WebhookDeadLetterRepository.test.js.map +1 -0
- package/package.json +9 -5
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import SkillParser from './SkillParser.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Helpers
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function makeSkillMd(frontmatter, body = '# My Skill\n\nDoes things.') {
|
|
7
|
+
return `---\n${frontmatter}\n---\n\n${body}`;
|
|
8
|
+
}
|
|
9
|
+
const MINIMAL_VALID = makeSkillMd('name: my-skill');
|
|
10
|
+
const FULL_VALID = makeSkillMd([
|
|
11
|
+
'name: full-skill',
|
|
12
|
+
'description: A thorough skill with all recommended fields present.',
|
|
13
|
+
'author: acme',
|
|
14
|
+
'version: 1.2.3',
|
|
15
|
+
'license: MIT',
|
|
16
|
+
'tags:',
|
|
17
|
+
' - typescript',
|
|
18
|
+
' - testing',
|
|
19
|
+
' - vitest',
|
|
20
|
+
'category: testing',
|
|
21
|
+
'repository: https://github.com/acme/full-skill',
|
|
22
|
+
].join('\n'));
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// extractFrontmatter
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
describe('SkillParser.extractFrontmatter', () => {
|
|
27
|
+
const parser = new SkillParser();
|
|
28
|
+
it('returns null when content does not start with ---', () => {
|
|
29
|
+
expect(parser.extractFrontmatter('name: foo\n---')).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
it('returns null when there is no closing --- delimiter', () => {
|
|
32
|
+
expect(parser.extractFrontmatter('---\nname: foo\n')).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it('returns null for an empty string', () => {
|
|
35
|
+
expect(parser.extractFrontmatter('')).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
it('parses a simple key-value pair', () => {
|
|
38
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: my-skill'));
|
|
39
|
+
expect(result?.name).toBe('my-skill');
|
|
40
|
+
});
|
|
41
|
+
it('parses double-quoted string values', () => {
|
|
42
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: "quoted skill"'));
|
|
43
|
+
expect(result?.name).toBe('quoted skill');
|
|
44
|
+
});
|
|
45
|
+
it('parses single-quoted string values', () => {
|
|
46
|
+
const result = parser.extractFrontmatter(makeSkillMd("name: 'single quoted'"));
|
|
47
|
+
expect(result?.name).toBe('single quoted');
|
|
48
|
+
});
|
|
49
|
+
it('parses boolean true', () => {
|
|
50
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: x\ndeprecated: true'));
|
|
51
|
+
expect(result?.deprecated).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
it('parses boolean false', () => {
|
|
54
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: x\ndeprecated: false'));
|
|
55
|
+
expect(result?.deprecated).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
it('parses a numeric value', () => {
|
|
58
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: x\nsome_count: 42'));
|
|
59
|
+
expect(result?.some_count).toBe(42);
|
|
60
|
+
});
|
|
61
|
+
it('parses a block-style array', () => {
|
|
62
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: x\ntags:\n - alpha\n - beta\n - gamma'));
|
|
63
|
+
expect(result?.tags).toEqual(['alpha', 'beta', 'gamma']);
|
|
64
|
+
});
|
|
65
|
+
it('parses an inline array', () => {
|
|
66
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: x\ntags: [alpha, beta, gamma]'));
|
|
67
|
+
expect(result?.tags).toEqual(['alpha', 'beta', 'gamma']);
|
|
68
|
+
});
|
|
69
|
+
it('ignores YAML comments', () => {
|
|
70
|
+
const result = parser.extractFrontmatter(makeSkillMd('# this is a comment\nname: commented-skill'));
|
|
71
|
+
expect(result?.name).toBe('commented-skill');
|
|
72
|
+
});
|
|
73
|
+
it('preserves unicode characters in string values', () => {
|
|
74
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: "日本語スキル"'));
|
|
75
|
+
expect(result?.name).toBe('日本語スキル');
|
|
76
|
+
});
|
|
77
|
+
it('preserves unknown extra keys', () => {
|
|
78
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: x\ncustom_field: hello'));
|
|
79
|
+
expect(result?.custom_field).toBe('hello');
|
|
80
|
+
});
|
|
81
|
+
it('coerces a numeric name to a string', () => {
|
|
82
|
+
const result = parser.extractFrontmatter(makeSkillMd('name: 123'));
|
|
83
|
+
expect(typeof result?.name).toBe('string');
|
|
84
|
+
expect(result?.name).toBe('123');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// validate
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
describe('SkillParser.validate', () => {
|
|
91
|
+
it('is valid for a frontmatter object with a name', () => {
|
|
92
|
+
const parser = new SkillParser();
|
|
93
|
+
const result = parser.validate({ name: 'my-skill' });
|
|
94
|
+
expect(result.valid).toBe(true);
|
|
95
|
+
expect(result.errors).toHaveLength(0);
|
|
96
|
+
});
|
|
97
|
+
it('returns an error when name is missing and requireName is true (default)', () => {
|
|
98
|
+
const parser = new SkillParser();
|
|
99
|
+
const result = parser.validate({});
|
|
100
|
+
expect(result.valid).toBe(false);
|
|
101
|
+
expect(result.errors).toContain('Missing required field: name');
|
|
102
|
+
});
|
|
103
|
+
it('does not error on missing name when requireName is false', () => {
|
|
104
|
+
const parser = new SkillParser({ requireName: false });
|
|
105
|
+
const result = parser.validate({});
|
|
106
|
+
expect(result.valid).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
it('returns an error when description is required but missing', () => {
|
|
109
|
+
const parser = new SkillParser({ requireDescription: true });
|
|
110
|
+
const result = parser.validate({ name: 'x' });
|
|
111
|
+
expect(result.valid).toBe(false);
|
|
112
|
+
expect(result.errors).toContain('Missing required field: description');
|
|
113
|
+
});
|
|
114
|
+
it('does not error when description is provided and required', () => {
|
|
115
|
+
const parser = new SkillParser({ requireDescription: true });
|
|
116
|
+
const result = parser.validate({ name: 'x', description: 'A skill.' });
|
|
117
|
+
expect(result.valid).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
it('emits a warning when description is absent', () => {
|
|
120
|
+
const parser = new SkillParser();
|
|
121
|
+
const result = parser.validate({ name: 'x' });
|
|
122
|
+
expect(result.warnings.some((w) => w.includes('description'))).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
it('emits a warning when version is absent', () => {
|
|
125
|
+
const parser = new SkillParser();
|
|
126
|
+
const result = parser.validate({ name: 'x' });
|
|
127
|
+
expect(result.warnings.some((w) => w.includes('version'))).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
it('emits a warning when tags are absent', () => {
|
|
130
|
+
const parser = new SkillParser();
|
|
131
|
+
const result = parser.validate({ name: 'x' });
|
|
132
|
+
expect(result.warnings.some((w) => w.includes('tags'))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
it('emits a deprecation warning when composes is present', () => {
|
|
135
|
+
const parser = new SkillParser();
|
|
136
|
+
const result = parser.validate({
|
|
137
|
+
name: 'x',
|
|
138
|
+
composes: ['other-skill'],
|
|
139
|
+
});
|
|
140
|
+
expect(result.warnings.some((w) => w.includes('composes'))).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
it('merges errors and warnings from a custom validator', () => {
|
|
143
|
+
const parser = new SkillParser({
|
|
144
|
+
customValidator: () => ({
|
|
145
|
+
valid: false,
|
|
146
|
+
errors: ['custom error'],
|
|
147
|
+
warnings: ['custom warning'],
|
|
148
|
+
}),
|
|
149
|
+
});
|
|
150
|
+
const result = parser.validate({ name: 'x' });
|
|
151
|
+
expect(result.errors).toContain('custom error');
|
|
152
|
+
expect(result.warnings).toContain('custom warning');
|
|
153
|
+
expect(result.valid).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
it('is valid when custom validator passes', () => {
|
|
156
|
+
const parser = new SkillParser({
|
|
157
|
+
customValidator: () => ({ valid: true, errors: [], warnings: [] }),
|
|
158
|
+
});
|
|
159
|
+
const result = parser.validate({ name: 'x' });
|
|
160
|
+
expect(result.valid).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
// parse
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
describe('SkillParser.parse', () => {
|
|
167
|
+
const parser = new SkillParser();
|
|
168
|
+
it('returns null for content without a frontmatter block', () => {
|
|
169
|
+
expect(parser.parse('# Just a heading\n\nNo frontmatter here.')).toBeNull();
|
|
170
|
+
});
|
|
171
|
+
it('returns null when required name field is absent', () => {
|
|
172
|
+
expect(parser.parse(makeSkillMd('description: no name here'))).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
it('returns ParsedSkillMetadata for valid minimal content', () => {
|
|
175
|
+
const result = parser.parse(MINIMAL_VALID);
|
|
176
|
+
expect(result).not.toBeNull();
|
|
177
|
+
expect(result?.name).toBe('my-skill');
|
|
178
|
+
});
|
|
179
|
+
it('maps all optional fields correctly', () => {
|
|
180
|
+
const result = parser.parse(FULL_VALID);
|
|
181
|
+
expect(result.name).toBe('full-skill');
|
|
182
|
+
expect(result.description).toBe('A thorough skill with all recommended fields present.');
|
|
183
|
+
expect(result.author).toBe('acme');
|
|
184
|
+
expect(result.version).toBe('1.2.3');
|
|
185
|
+
expect(result.license).toBe('MIT');
|
|
186
|
+
expect(result.tags).toEqual(['typescript', 'testing', 'vitest']);
|
|
187
|
+
expect(result.category).toBe('testing');
|
|
188
|
+
expect(result.repository).toBe('https://github.com/acme/full-skill');
|
|
189
|
+
});
|
|
190
|
+
it('sets absent optional fields to null', () => {
|
|
191
|
+
const result = parser.parse(MINIMAL_VALID);
|
|
192
|
+
expect(result.description).toBeNull();
|
|
193
|
+
expect(result.author).toBeNull();
|
|
194
|
+
expect(result.version).toBeNull();
|
|
195
|
+
expect(result.license).toBeNull();
|
|
196
|
+
expect(result.category).toBeNull();
|
|
197
|
+
expect(result.repository).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
it('sets tags to an empty array when absent', () => {
|
|
200
|
+
const result = parser.parse(MINIMAL_VALID);
|
|
201
|
+
expect(result.tags).toEqual([]);
|
|
202
|
+
});
|
|
203
|
+
it('preserves rawContent in the result', () => {
|
|
204
|
+
const result = parser.parse(MINIMAL_VALID);
|
|
205
|
+
expect(result.rawContent).toBe(MINIMAL_VALID);
|
|
206
|
+
});
|
|
207
|
+
it('includes the original frontmatter object in the result', () => {
|
|
208
|
+
const result = parser.parse(MINIMAL_VALID);
|
|
209
|
+
expect(result.frontmatter.name).toBe('my-skill');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// parseWithValidation
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
describe('SkillParser.parseWithValidation', () => {
|
|
216
|
+
const parser = new SkillParser();
|
|
217
|
+
it('returns failed validation when frontmatter is missing', () => {
|
|
218
|
+
const { metadata, validation, frontmatter } = parser.parseWithValidation('No frontmatter here.');
|
|
219
|
+
expect(metadata).toBeNull();
|
|
220
|
+
expect(frontmatter).toBeNull();
|
|
221
|
+
expect(validation.valid).toBe(false);
|
|
222
|
+
expect(validation.errors.length).toBeGreaterThan(0);
|
|
223
|
+
});
|
|
224
|
+
it('returns null metadata and errors when validation fails', () => {
|
|
225
|
+
const { metadata, validation } = parser.parseWithValidation(makeSkillMd('description: missing name'));
|
|
226
|
+
expect(metadata).toBeNull();
|
|
227
|
+
expect(validation.valid).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
it('returns non-null metadata and the frontmatter when valid', () => {
|
|
230
|
+
const { metadata, validation, frontmatter } = parser.parseWithValidation(MINIMAL_VALID);
|
|
231
|
+
expect(metadata).not.toBeNull();
|
|
232
|
+
expect(validation.valid).toBe(true);
|
|
233
|
+
expect(frontmatter?.name).toBe('my-skill');
|
|
234
|
+
});
|
|
235
|
+
it('includes warnings alongside a successful parse', () => {
|
|
236
|
+
const { validation } = parser.parseWithValidation(MINIMAL_VALID);
|
|
237
|
+
expect(validation.warnings.length).toBeGreaterThan(0);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// extractBody
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
describe('SkillParser.extractBody', () => {
|
|
244
|
+
const parser = new SkillParser();
|
|
245
|
+
it('returns content after the closing --- delimiter', () => {
|
|
246
|
+
const content = '---\nname: x\n---\n\n# Body\n\nSome content.';
|
|
247
|
+
expect(parser.extractBody(content)).toBe('# Body\n\nSome content.');
|
|
248
|
+
});
|
|
249
|
+
it('returns the full content when no frontmatter delimiter is present', () => {
|
|
250
|
+
const content = '# Just a body\n\nNo frontmatter.';
|
|
251
|
+
expect(parser.extractBody(content)).toBe(content);
|
|
252
|
+
});
|
|
253
|
+
it('returns an empty string when there is no body after the frontmatter', () => {
|
|
254
|
+
const content = '---\nname: x\n---';
|
|
255
|
+
expect(parser.extractBody(content)).toBe('');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// inferTrustTier
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
describe('SkillParser.inferTrustTier', () => {
|
|
262
|
+
const parser = new SkillParser();
|
|
263
|
+
function makeMetadata(overrides) {
|
|
264
|
+
return {
|
|
265
|
+
name: 'test-skill',
|
|
266
|
+
description: null,
|
|
267
|
+
author: null,
|
|
268
|
+
version: null,
|
|
269
|
+
tags: [],
|
|
270
|
+
category: null,
|
|
271
|
+
license: null,
|
|
272
|
+
repository: null,
|
|
273
|
+
rawContent: '',
|
|
274
|
+
frontmatter: { name: 'test-skill' },
|
|
275
|
+
...overrides,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
it('returns verified for an anthropic author', () => {
|
|
279
|
+
expect(parser.inferTrustTier(makeMetadata({ author: 'anthropic' }))).toBe('verified');
|
|
280
|
+
});
|
|
281
|
+
it('returns verified for a skillsmith author', () => {
|
|
282
|
+
expect(parser.inferTrustTier(makeMetadata({ author: 'skillsmith' }))).toBe('verified');
|
|
283
|
+
});
|
|
284
|
+
it('is case-insensitive for verified author matching', () => {
|
|
285
|
+
expect(parser.inferTrustTier(makeMetadata({ author: 'Anthropic' }))).toBe('verified');
|
|
286
|
+
});
|
|
287
|
+
it('returns community when metadata score is 3 or more', () => {
|
|
288
|
+
const metadata = makeMetadata({
|
|
289
|
+
author: 'acme',
|
|
290
|
+
// description > 50 chars, 3+ tags, version, license = score 4
|
|
291
|
+
description: 'A well-documented skill that exceeds the fifty character threshold.',
|
|
292
|
+
tags: ['a', 'b', 'c'],
|
|
293
|
+
version: '1.0.0',
|
|
294
|
+
license: 'MIT',
|
|
295
|
+
});
|
|
296
|
+
expect(parser.inferTrustTier(metadata)).toBe('community');
|
|
297
|
+
});
|
|
298
|
+
it('returns experimental when metadata score is exactly 1', () => {
|
|
299
|
+
const metadata = makeMetadata({
|
|
300
|
+
author: 'acme',
|
|
301
|
+
version: '1.0.0',
|
|
302
|
+
// description null, no tags, no license = score 1
|
|
303
|
+
});
|
|
304
|
+
expect(parser.inferTrustTier(metadata)).toBe('experimental');
|
|
305
|
+
});
|
|
306
|
+
it('returns unknown when there is no meaningful metadata', () => {
|
|
307
|
+
expect(parser.inferTrustTier(makeMetadata({ author: 'acme' }))).toBe('unknown');
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// SkillParser.parseDependencyBlock (static)
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
describe('SkillParser.parseDependencyBlock', () => {
|
|
314
|
+
it('returns undefined when there is no dependencies key', () => {
|
|
315
|
+
expect(SkillParser.parseDependencyBlock('name: x\nversion: 1.0.0')).toBeUndefined();
|
|
316
|
+
});
|
|
317
|
+
it('returns undefined when dependencies is not an object', () => {
|
|
318
|
+
expect(SkillParser.parseDependencyBlock('dependencies: some-string')).toBeUndefined();
|
|
319
|
+
});
|
|
320
|
+
it('returns a parsed array when dependencies has a flat array item', () => {
|
|
321
|
+
// The built-in parser is best-effort; nested objects need js-yaml.
|
|
322
|
+
// A flat array under dependencies is the simplest case it can handle.
|
|
323
|
+
const yaml = 'dependencies:\n - some-skill';
|
|
324
|
+
const result = SkillParser.parseDependencyBlock(yaml);
|
|
325
|
+
expect(result).toEqual(['some-skill']);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// SkillParser.checkReferences (static)
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
describe('SkillParser.checkReferences', () => {
|
|
332
|
+
it('returns empty warnings and matches for clean content', () => {
|
|
333
|
+
const { warnings, matches } = SkillParser.checkReferences('# A clean skill\n\nNo refs here.');
|
|
334
|
+
expect(warnings).toHaveLength(0);
|
|
335
|
+
expect(matches).toHaveLength(0);
|
|
336
|
+
});
|
|
337
|
+
it('detects a Docker container name', () => {
|
|
338
|
+
const { matches } = SkillParser.checkReferences('Run `docker exec myproject-dev-1 npm test`.');
|
|
339
|
+
expect(matches.some((m) => m.pattern === 'Docker container name')).toBe(true);
|
|
340
|
+
});
|
|
341
|
+
it('detects an npm package scope', () => {
|
|
342
|
+
const { matches } = SkillParser.checkReferences('Install `@skillsmith-tools/helper` first.');
|
|
343
|
+
expect(matches.some((m) => m.pattern === 'npm package scope')).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
it('detects a GitHub repo reference', () => {
|
|
346
|
+
const { matches } = SkillParser.checkReferences('See github.com/acme-corp/my-repo for details.');
|
|
347
|
+
expect(matches.some((m) => m.pattern === 'GitHub repo reference')).toBe(true);
|
|
348
|
+
});
|
|
349
|
+
it('detects a project URL', () => {
|
|
350
|
+
const { matches } = SkillParser.checkReferences('Documentation at https://myproject.app/docs/guide.');
|
|
351
|
+
expect(matches.some((m) => m.pattern === 'Project URL')).toBe(true);
|
|
352
|
+
});
|
|
353
|
+
it('includes the correct line number in match results', () => {
|
|
354
|
+
const content = 'Line one.\nSee github.com/acme-corp/my-repo here.\nLine three.';
|
|
355
|
+
const { matches } = SkillParser.checkReferences(content);
|
|
356
|
+
const ghMatch = matches.find((m) => m.pattern === 'GitHub repo reference');
|
|
357
|
+
expect(ghMatch?.line).toBe(2);
|
|
358
|
+
});
|
|
359
|
+
it('truncates matched text longer than 80 characters', () => {
|
|
360
|
+
const longUrl = 'https://very-long-project.app/' + 'x'.repeat(100) + '/';
|
|
361
|
+
const { matches } = SkillParser.checkReferences(`See ${longUrl} for info.`);
|
|
362
|
+
const urlMatch = matches.find((m) => m.pattern === 'Project URL');
|
|
363
|
+
expect(urlMatch).toBeDefined();
|
|
364
|
+
expect(urlMatch.text.length).toBeLessThanOrEqual(83); // 80 chars + '...'
|
|
365
|
+
});
|
|
366
|
+
it('applies custom patterns alongside the defaults', () => {
|
|
367
|
+
const { matches } = SkillParser.checkReferences('The secret token is abc-123.', [/abc-\d+/g]);
|
|
368
|
+
expect(matches.some((m) => m.pattern === 'Custom pattern')).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
it('includes a summary warning when matches are found', () => {
|
|
371
|
+
const { warnings } = SkillParser.checkReferences('See github.com/acme-corp/my-repo for details.');
|
|
372
|
+
expect(warnings.length).toBeGreaterThan(0);
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
//# sourceMappingURL=SkillParser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillParser.test.js","sourceRoot":"","sources":["../../../src/indexer/SkillParser.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,WAAW,MAAM,kBAAkB,CAAA;AAG1C,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E,SAAS,WAAW,CAAC,WAAmB,EAAE,IAAI,GAAG,4BAA4B;IAC3E,OAAO,QAAQ,WAAW,YAAY,IAAI,EAAE,CAAA;AAC9C,CAAC;AAED,MAAM,aAAa,GAAG,WAAW,CAAC,gBAAgB,CAAC,CAAA;AAEnD,MAAM,UAAU,GAAG,WAAW,CAC5B;IACE,kBAAkB;IAClB,oEAAoE;IACpE,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,OAAO;IACP,gBAAgB;IAChB,aAAa;IACb,YAAY;IACZ,mBAAmB;IACnB,gDAAgD;CACjD,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAA;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;IAEhC,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAChE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAClE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAClD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAA;QACvE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;QAC7E,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC,CAAA;QAC9E,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAAC,CAAA;QAClF,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,4BAA4B,CAAC,CAAC,CAAA;QACnF,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC,CAAA;QAChF,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CACtC,WAAW,CAAC,gDAAgD,CAAC,CAC9D,CAAA;QACD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,qCAAqC,CAAC,CAAC,CAAA;QAC5F,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC1D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CACtC,WAAW,CAAC,4CAA4C,CAAC,CAC1D,CAAA;QACD,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAA;QACvE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,8BAA8B,CAAC,CAAC,CAAA;QACrF,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,CAAA;QAClE,MAAM,CAAC,OAAO,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC1C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAsB,CAAC,CAAA;QACxE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAsB,CAAC,CAAA;QACtD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAA;IACjE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAA;QACtD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAsB,CAAC,CAAA;QACtD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAsB,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5D,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAsB,CAAC,CAAA;QAC1F,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAsB,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAsB,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACvE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAsB,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;YAC7B,IAAI,EAAE,GAAG;YACT,QAAQ,EAAE,CAAC,aAAa,CAAC;SACN,CAAC,CAAA;QACtB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC;YAC7B,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;gBACtB,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,CAAC,cAAc,CAAC;gBACxB,QAAQ,EAAE,CAAC,gBAAgB,CAAC;aAC7B,CAAC;SACH,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAsB,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;QAC/C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAA;QACnD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAClC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC;YAC7B,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;SACnE,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,GAAG,EAAsB,CAAC,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;IAEhC,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC7E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,CAAA;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC7B,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAwB,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QACtC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAA;QACxF,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAClC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACpC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAA;QAChE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACvC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAwB,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAA;QACrC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAA;QAChC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAA;QACjC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAA;QACjC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAA;QAClC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAwB,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAwB,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,aAAa,CAAwB,CAAA;QACjE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAClD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,sBAAsB;AACtB,8EAA8E;AAE9E,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;IAEhC,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,mBAAmB,CAAC,sBAAsB,CAAC,CAAA;QAChG,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC3B,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC9B,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACpC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,mBAAmB,CACzD,WAAW,CAAC,2BAA2B,CAAC,CACzC,CAAA;QACD,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC3B,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAA;QACvF,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;QAC/B,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAA;QAChE,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;IAEhC,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,OAAO,GAAG,8CAA8C,CAAA;QAC9D,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,OAAO,GAAG,kCAAkC,CAAA;QAClD,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,OAAO,GAAG,mBAAmB,CAAA;QACnC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IAC1C,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAA;IAEhC,SAAS,YAAY,CAAC,SAAuC;QAC3D,OAAO;YACL,IAAI,EAAE,YAAY;YAClB,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,EAAE;YACR,QAAQ,EAAE,IAAI;YACd,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,IAAI;YAChB,UAAU,EAAE,EAAE;YACd,WAAW,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE;YACnC,GAAG,SAAS;SACb,CAAA;IACH,CAAC;IAED,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACvF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACvF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,QAAQ,GAAG,YAAY,CAAC;YAC5B,MAAM,EAAE,MAAM;YACd,8DAA8D;YAC9D,WAAW,EAAE,qEAAqE;YAClF,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;YACrB,OAAO,EAAE,OAAO;YAChB,OAAO,EAAE,KAAK;SACf,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,QAAQ,GAAG,YAAY,CAAC;YAC5B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,OAAO;YAChB,kDAAkD;SACnD,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACjF,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,4CAA4C;AAC5C,8EAA8E;AAE9E,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,yBAAyB,CAAC,CAAC,CAAC,aAAa,EAAE,CAAA;IACrF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,WAAW,CAAC,oBAAoB,CAAC,2BAA2B,CAAC,CAAC,CAAC,aAAa,EAAE,CAAA;IACvF,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gEAAgE,EAAE,GAAG,EAAE;QACxE,mEAAmE;QACnE,sEAAsE;QACtE,MAAM,IAAI,GAAG,+BAA+B,CAAA;QAC5C,MAAM,MAAM,GAAG,WAAW,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAA;QACrD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAA;IACxC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;IAC3C,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,kCAAkC,CAAC,CAAA;QAC7F,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,6CAA6C,CAAC,CAAA;QAC9F,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,2CAA2C,CAAC,CAAA;QAC5F,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,+CAA+C,CAAC,CAAA;QAChG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC/E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAC7C,oDAAoD,CACrD,CAAA;QACD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,OAAO,GAAG,gEAAgE,CAAA;QAChF,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,OAAO,CAAC,CAAA;QACxD,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,uBAAuB,CAAC,CAAA;QAC1E,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC/B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,OAAO,GAAG,gCAAgC,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;QACxE,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,OAAO,OAAO,YAAY,CAAC,CAAA;QAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,CAAC,CAAA;QACjE,MAAM,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAA;QAC9B,MAAM,CAAC,QAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAA,CAAC,mBAAmB;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,eAAe,CAAC,8BAA8B,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;QAC7F,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAC,eAAe,CAC9C,+CAA+C,CAChD,CAAA;QACD,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Local Filesystem Source Adapter (SMI-591)
|
|
2
|
+
* Local Filesystem Source Adapter (SMI-591, SMI-4287, SMI-4319, SMI-4320)
|
|
3
3
|
*
|
|
4
4
|
* Scans local directories for SKILL.md files.
|
|
5
5
|
* Useful for local development and testing.
|
|
6
|
+
*
|
|
7
|
+
* SMI-4287 hardening:
|
|
8
|
+
* - Symlink targets are resolved via `fs.realpath` and checked against the
|
|
9
|
+
* adapter's `rootDir`. Targets outside root are skipped with a
|
|
10
|
+
* `symlink-escape` warning (unless `allowSymlinksOutsideRoot` is `true`).
|
|
11
|
+
* - Permission (EACCES/EPERM), not-found (ENOENT), and loop (ELOOP) errors
|
|
12
|
+
* are surfaced as `AdapterError` entries on `SourceSearchResult.warnings`
|
|
13
|
+
* instead of throwing, so siblings continue to be scanned.
|
|
14
|
+
* - All `fs.*` calls route through the typed `safeFs` helpers; the historic
|
|
15
|
+
* bare `try/catch` for EACCES in `scanDirectory` is removed.
|
|
16
|
+
*
|
|
17
|
+
* SMI-4319 hardening:
|
|
18
|
+
* - `runScan` allocates a fresh `visitedRealpaths: Set<string>` per
|
|
19
|
+
* invocation so mutually-recursive / self-looping directory symlinks are
|
|
20
|
+
* detected and skipped with a `loop` warning instead of silently wasting
|
|
21
|
+
* `maxDepth` traversals.
|
|
22
|
+
*
|
|
23
|
+
* SMI-4320 hardening:
|
|
24
|
+
* - `resolveSkillPath` is now async and routes through `resolveSafeRealpath`
|
|
25
|
+
* (byte-wise `startsWith(rootReal + sep)` on realpath outputs — no
|
|
26
|
+
* platform lowercasing). Direct-access methods (`getRepository`,
|
|
27
|
+
* `fetchSkillContent`, `skillExists`) inherit containment instead of
|
|
28
|
+
* relying solely on lexical `validatePath`. This closes the scan-to-fetch
|
|
29
|
+
* TOCTOU window where an indexed-then-swapped symlink previously escaped
|
|
30
|
+
* containment. `allowSymlinksOutsideRoot` is honoured at every realpath
|
|
31
|
+
* callsite. Residual TOCTOU between `resolveSkillPath` and the subsequent
|
|
32
|
+
* `fs.readFile` is documented; closing it requires fd-based I/O and is
|
|
33
|
+
* tracked as a separate follow-up.
|
|
6
34
|
*/
|
|
7
35
|
import { BaseSourceAdapter } from './BaseSourceAdapter.js';
|
|
8
36
|
import type { SourceConfig, SourceLocation, SourceRepository, SourceSearchOptions, SourceSearchResult, SkillContent, SourceHealth } from './types.js';
|
|
@@ -18,6 +46,22 @@ export interface LocalFilesystemConfig extends SourceConfig {
|
|
|
18
46
|
excludePatterns?: string[];
|
|
19
47
|
/** Whether to follow symlinks (default: false) */
|
|
20
48
|
followSymlinks?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Allow symlinks whose target resolves outside `rootDir` (SMI-4287).
|
|
51
|
+
*
|
|
52
|
+
* Default `false`: symlinks pointing outside the scan root are skipped and
|
|
53
|
+
* a `symlink-escape` entry is added to `SourceSearchResult.warnings`. This
|
|
54
|
+
* prevents an attacker with write access to `rootDir` from exfiltrating
|
|
55
|
+
* content from arbitrary locations on the filesystem (GitHub #600).
|
|
56
|
+
*
|
|
57
|
+
* Set to `true` only if you trust every symlink inside `rootDir` (e.g.
|
|
58
|
+
* monorepo layouts that intentionally point at sibling packages). The
|
|
59
|
+
* caller accepts the security tradeoff.
|
|
60
|
+
*
|
|
61
|
+
* Note: this flag has no effect when `followSymlinks` is `false` — symlinks
|
|
62
|
+
* are never traversed in that case.
|
|
63
|
+
*/
|
|
64
|
+
allowSymlinksOutsideRoot?: boolean;
|
|
21
65
|
}
|
|
22
66
|
/**
|
|
23
67
|
* Local Filesystem Source Adapter
|
|
@@ -37,6 +81,9 @@ export interface LocalFilesystemConfig extends SourceConfig {
|
|
|
37
81
|
*
|
|
38
82
|
* await adapter.initialize()
|
|
39
83
|
* const result = await adapter.search({})
|
|
84
|
+
* for (const warning of result.warnings ?? []) {
|
|
85
|
+
* console.warn(`[${warning.code}] ${warning.message}`)
|
|
86
|
+
* }
|
|
40
87
|
* ```
|
|
41
88
|
*/
|
|
42
89
|
export declare class LocalFilesystemAdapter extends BaseSourceAdapter {
|
|
@@ -44,26 +91,47 @@ export declare class LocalFilesystemAdapter extends BaseSourceAdapter {
|
|
|
44
91
|
private readonly maxDepth;
|
|
45
92
|
private readonly excludePatterns;
|
|
46
93
|
private readonly followSymlinks;
|
|
94
|
+
private readonly allowSymlinksOutsideRoot;
|
|
47
95
|
private discoveredSkills;
|
|
96
|
+
/**
|
|
97
|
+
* Warnings accumulated during the most recent scan. Consumed and cleared
|
|
98
|
+
* by `search()` so each caller sees only the warnings from that call's
|
|
99
|
+
* underlying scan.
|
|
100
|
+
*/
|
|
101
|
+
private scanWarnings;
|
|
48
102
|
constructor(config: LocalFilesystemConfig);
|
|
49
103
|
/**
|
|
50
104
|
* Initialize by scanning the filesystem
|
|
51
105
|
*/
|
|
52
106
|
protected doInitialize(): Promise<void>;
|
|
53
107
|
/**
|
|
54
|
-
* Check if root directory exists and is accessible
|
|
108
|
+
* Check if root directory exists and is accessible.
|
|
109
|
+
*
|
|
110
|
+
* SMI-4287: routes `fs.stat(rootDir)` through `safeFs` so the raw Node
|
|
111
|
+
* error is translated to a typed `AdapterError` message.
|
|
55
112
|
*/
|
|
56
113
|
protected doHealthCheck(): Promise<Partial<SourceHealth>>;
|
|
57
114
|
/**
|
|
58
|
-
* Search for skills in the scanned directories
|
|
115
|
+
* Search for skills in the scanned directories.
|
|
116
|
+
*
|
|
117
|
+
* SMI-4287: `warnings` collects non-fatal `AdapterError` entries from the
|
|
118
|
+
* scan (symlink escapes, permission denials, loops). An empty array is
|
|
119
|
+
* returned as `undefined` to keep the field strictly optional.
|
|
59
120
|
*/
|
|
60
121
|
search(options?: SourceSearchOptions): Promise<SourceSearchResult>;
|
|
61
122
|
/**
|
|
62
|
-
* Get repository info for a skill location
|
|
123
|
+
* Get repository info for a skill location.
|
|
124
|
+
*
|
|
125
|
+
* SMI-4287: `fs.stat` is routed through `safeFs`; permission errors are
|
|
126
|
+
* converted to typed Error messages instead of raw Node throws.
|
|
63
127
|
*/
|
|
64
128
|
getRepository(location: SourceLocation): Promise<SourceRepository>;
|
|
65
129
|
/**
|
|
66
|
-
* Fetch skill content from local file
|
|
130
|
+
* Fetch skill content from local file.
|
|
131
|
+
*
|
|
132
|
+
* SMI-4287: both `fs.readFile` and `fs.stat` route through `safeFs`, so
|
|
133
|
+
* permission errors (EACCES/EPERM) raise typed Errors with path context
|
|
134
|
+
* instead of raw Node errors.
|
|
67
135
|
*/
|
|
68
136
|
fetchSkillContent(location: SourceLocation): Promise<SkillContent>;
|
|
69
137
|
/**
|
|
@@ -71,7 +139,10 @@ export declare class LocalFilesystemAdapter extends BaseSourceAdapter {
|
|
|
71
139
|
*/
|
|
72
140
|
skillExists(location: SourceLocation): Promise<boolean>;
|
|
73
141
|
/**
|
|
74
|
-
* Rescan the filesystem for new skills
|
|
142
|
+
* Rescan the filesystem for new skills.
|
|
143
|
+
*
|
|
144
|
+
* Returns the count of discovered skills. Warnings from the rescan are
|
|
145
|
+
* available via the next call to `search()`.
|
|
75
146
|
*/
|
|
76
147
|
rescan(): Promise<number>;
|
|
77
148
|
/**
|
|
@@ -79,9 +150,16 @@ export declare class LocalFilesystemAdapter extends BaseSourceAdapter {
|
|
|
79
150
|
*/
|
|
80
151
|
get skillCount(): number;
|
|
81
152
|
/**
|
|
82
|
-
*
|
|
153
|
+
* Run the recursive scan starting at `rootDir`. Delegates to the extracted
|
|
154
|
+
* `scanDirectoryRecursive` helper (see `LocalFilesystemAdapter.scan.ts`).
|
|
155
|
+
*
|
|
156
|
+
* SMI-4319: allocates a fresh `visitedRealpaths` set per invocation so
|
|
157
|
+
* back-to-back scans don't share state. Sibling directories within a
|
|
158
|
+
* single scan share the set (they're in the same call tree), so
|
|
159
|
+
* cross-linked loops (A↔B) are caught even when the loop isn't on the
|
|
160
|
+
* descent path from `rootDir`.
|
|
83
161
|
*/
|
|
84
|
-
private
|
|
162
|
+
private runScan;
|
|
85
163
|
/**
|
|
86
164
|
* Check if a path/name should be excluded (SMI-722, SMI-726)
|
|
87
165
|
* Uses centralized safe pattern matching to prevent RegExp injection
|
|
@@ -89,11 +167,27 @@ export declare class LocalFilesystemAdapter extends BaseSourceAdapter {
|
|
|
89
167
|
private isExcluded;
|
|
90
168
|
/**
|
|
91
169
|
* Resolve a skill location to a full filesystem path
|
|
92
|
-
*
|
|
170
|
+
* (SMI-720, SMI-726, SMI-4287, SMI-4320).
|
|
171
|
+
*
|
|
172
|
+
* Two-stage containment: (1) lexical `validatePath` fast-fails
|
|
173
|
+
* `../`-style traversal (SMI-720 contract — callers assert the
|
|
174
|
+
* "Path traversal detected" message), then (2) `resolveSafeRealpath`
|
|
175
|
+
* enforces realpath byte-wise containment so symlinks can't escape
|
|
176
|
+
* `rootDir` even when the lexical path is clean. Honours the SMI-4287
|
|
177
|
+
* `allowSymlinksOutsideRoot` opt-in.
|
|
178
|
+
*
|
|
179
|
+
* Not-found behaviour: realpath ENOENT falls back to the lexically
|
|
180
|
+
* resolved path so downstream `stat` / `readFile` produce the canonical
|
|
181
|
+
* caller-visible error. TOCTOU caveat: the window between this resolve
|
|
182
|
+
* and the caller's subsequent read remains open; closing it requires
|
|
183
|
+
* fd-based I/O and is tracked separately.
|
|
93
184
|
*/
|
|
94
185
|
private resolveSkillPath;
|
|
95
186
|
/**
|
|
96
|
-
* Convert discovered skill to SourceRepository
|
|
187
|
+
* Convert discovered skill to SourceRepository.
|
|
188
|
+
*
|
|
189
|
+
* Returns both the repository and any warnings encountered while reading
|
|
190
|
+
* the file (typically permission errors on SKILL.md after discovery).
|
|
97
191
|
*/
|
|
98
192
|
private skillToRepository;
|
|
99
193
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalFilesystemAdapter.d.ts","sourceRoot":"","sources":["../../../src/sources/LocalFilesystemAdapter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"LocalFilesystemAdapter.d.ts","sourceRoot":"","sources":["../../../src/sources/LocalFilesystemAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EAEb,MAAM,YAAY,CAAA;AAanB;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uCAAuC;IACvC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,kDAAkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;;;;;;;;OAcG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAA;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,sBAAuB,SAAQ,iBAAiB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAS;IAClD,OAAO,CAAC,gBAAgB,CAA8B;IACtD;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAqB;gBAE7B,MAAM,EAAE,qBAAqB;IASzC;;OAEG;cACsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtD;;;;;OAKG;cACa,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAc/D;;;;;;OAMG;IACG,MAAM,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmC5E;;;;;OAKG;IACG,aAAa,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkCxE;;;;;;OAMG;IACG,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAyBxE;;OAEG;IACY,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAUtE;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAO/B;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED;;;;;;;;;OASG;YACW,OAAO;IAcrB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;;;;;;;;;;;;OAgBG;YACW,gBAAgB;IA2C9B;;;;;OAKG;YACW,iBAAiB;IAiD/B;;OAEG;IACH,OAAO,CAAC,UAAU;IAIlB;;OAEG;IACH,OAAO,CAAC,WAAW;CAGpB;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,qBAAqB,GAC5B,sBAAsB,CAMxB"}
|