@kjerneverk/riotplan-catalyst 1.0.0-dev.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/.nvmrc +1 -0
- package/README.md +327 -0
- package/eslint.config.mjs +38 -0
- package/package.json +54 -0
- package/src/loader/catalyst-loader.ts +261 -0
- package/src/loader/plan-manifest.ts +228 -0
- package/src/merger/facet-merger.ts +225 -0
- package/src/riotplan-catalyst.ts +59 -0
- package/src/schema/schemas.ts +143 -0
- package/src/types.ts +140 -0
- package/tests/catalyst-loader.test.ts +243 -0
- package/tests/facet-merger.test.ts +311 -0
- package/tests/fixtures/complete-catalyst/catalyst.yml +11 -0
- package/tests/fixtures/complete-catalyst/constraints/documentation.md +7 -0
- package/tests/fixtures/complete-catalyst/constraints/testing.md +5 -0
- package/tests/fixtures/complete-catalyst/domain-knowledge/overview.md +11 -0
- package/tests/fixtures/complete-catalyst/output-templates/press-release.md +16 -0
- package/tests/fixtures/complete-catalyst/process-guidance/lifecycle.md +13 -0
- package/tests/fixtures/complete-catalyst/questions/exploration.md +10 -0
- package/tests/fixtures/complete-catalyst/questions/shaping.md +5 -0
- package/tests/fixtures/complete-catalyst/validation-rules/checklist.md +11 -0
- package/tests/fixtures/invalid-catalyst/questions/some-questions.md +3 -0
- package/tests/fixtures/partial-catalyst/catalyst.yml +7 -0
- package/tests/fixtures/partial-catalyst/constraints/general.md +4 -0
- package/tests/fixtures/partial-catalyst/questions/basics.md +4 -0
- package/tests/plan-manifest.test.ts +315 -0
- package/tests/schema.test.ts +308 -0
- package/tests/setup.ts +1 -0
- package/tsconfig.json +22 -0
- package/vite.config.ts +43 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
CatalystManifestSchema,
|
|
4
|
+
PlanManifestSchema,
|
|
5
|
+
FacetsDeclarationSchema,
|
|
6
|
+
FACET_DIRECTORIES,
|
|
7
|
+
FACET_TYPES,
|
|
8
|
+
} from '@/schema/schemas';
|
|
9
|
+
|
|
10
|
+
describe('CatalystManifestSchema', () => {
|
|
11
|
+
describe('valid manifests', () => {
|
|
12
|
+
it('validates a complete manifest with all fields', () => {
|
|
13
|
+
const manifest = {
|
|
14
|
+
id: '@kjerneverk/catalyst-nodejs',
|
|
15
|
+
name: 'Node.js Catalyst',
|
|
16
|
+
description: 'Guidelines for Node.js development',
|
|
17
|
+
version: '1.0.0',
|
|
18
|
+
facets: {
|
|
19
|
+
questions: true,
|
|
20
|
+
constraints: true,
|
|
21
|
+
outputTemplates: false,
|
|
22
|
+
domainKnowledge: true,
|
|
23
|
+
processGuidance: false,
|
|
24
|
+
validationRules: true,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
29
|
+
expect(result.success).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('validates a minimal manifest without facets', () => {
|
|
33
|
+
const manifest = {
|
|
34
|
+
id: 'simple-catalyst',
|
|
35
|
+
name: 'Simple Catalyst',
|
|
36
|
+
description: 'A simple catalyst',
|
|
37
|
+
version: '1.0.0',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
41
|
+
expect(result.success).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('validates scoped package names', () => {
|
|
45
|
+
const manifest = {
|
|
46
|
+
id: '@my-org/my-catalyst',
|
|
47
|
+
name: 'My Catalyst',
|
|
48
|
+
description: 'A catalyst',
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('validates unscoped package names', () => {
|
|
57
|
+
const manifest = {
|
|
58
|
+
id: 'my-catalyst',
|
|
59
|
+
name: 'My Catalyst',
|
|
60
|
+
description: 'A catalyst',
|
|
61
|
+
version: '1.0.0',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
65
|
+
expect(result.success).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('validates dev versions', () => {
|
|
69
|
+
const manifest = {
|
|
70
|
+
id: 'catalyst',
|
|
71
|
+
name: 'Catalyst',
|
|
72
|
+
description: 'A catalyst',
|
|
73
|
+
version: '1.0.0-dev.0',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
77
|
+
expect(result.success).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('validates alpha/beta versions', () => {
|
|
81
|
+
const manifest = {
|
|
82
|
+
id: 'catalyst',
|
|
83
|
+
name: 'Catalyst',
|
|
84
|
+
description: 'A catalyst',
|
|
85
|
+
version: '2.0.0-alpha.1',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
89
|
+
expect(result.success).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('invalid manifests', () => {
|
|
94
|
+
it('rejects missing required fields', () => {
|
|
95
|
+
const manifest = {
|
|
96
|
+
id: '@kjerneverk/catalyst-nodejs',
|
|
97
|
+
// missing name, description, version
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
101
|
+
expect(result.success).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('rejects empty name', () => {
|
|
105
|
+
const manifest = {
|
|
106
|
+
id: 'catalyst',
|
|
107
|
+
name: '',
|
|
108
|
+
description: 'A catalyst',
|
|
109
|
+
version: '1.0.0',
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
113
|
+
expect(result.success).toBe(false);
|
|
114
|
+
if (!result.success) {
|
|
115
|
+
expect(result.error.issues[0].message).toContain('empty');
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('rejects empty description', () => {
|
|
120
|
+
const manifest = {
|
|
121
|
+
id: 'catalyst',
|
|
122
|
+
name: 'Catalyst',
|
|
123
|
+
description: '',
|
|
124
|
+
version: '1.0.0',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
128
|
+
expect(result.success).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('rejects invalid version format', () => {
|
|
132
|
+
const manifest = {
|
|
133
|
+
id: 'catalyst',
|
|
134
|
+
name: 'Catalyst',
|
|
135
|
+
description: 'A catalyst',
|
|
136
|
+
version: 'v1.0.0', // invalid: has 'v' prefix
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
140
|
+
expect(result.success).toBe(false);
|
|
141
|
+
if (!result.success) {
|
|
142
|
+
expect(result.error.issues[0].message).toContain('semver');
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('rejects invalid package name format', () => {
|
|
147
|
+
const manifest = {
|
|
148
|
+
id: '@invalid//package', // invalid: double slash
|
|
149
|
+
name: 'Catalyst',
|
|
150
|
+
description: 'A catalyst',
|
|
151
|
+
version: '1.0.0',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
155
|
+
expect(result.success).toBe(false);
|
|
156
|
+
if (!result.success) {
|
|
157
|
+
expect(result.error.issues[0].message).toContain('NPM package name');
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('rejects package names with spaces', () => {
|
|
162
|
+
const manifest = {
|
|
163
|
+
id: 'my catalyst',
|
|
164
|
+
name: 'Catalyst',
|
|
165
|
+
description: 'A catalyst',
|
|
166
|
+
version: '1.0.0',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result = CatalystManifestSchema.safeParse(manifest);
|
|
170
|
+
expect(result.success).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('PlanManifestSchema', () => {
|
|
176
|
+
describe('valid manifests', () => {
|
|
177
|
+
it('validates a complete plan manifest', () => {
|
|
178
|
+
const plan = {
|
|
179
|
+
id: 'my-plan',
|
|
180
|
+
title: 'My Plan',
|
|
181
|
+
catalysts: ['@kjerneverk/catalyst-nodejs', 'simple-catalyst'],
|
|
182
|
+
created: '2026-02-08T12:00:00Z',
|
|
183
|
+
metadata: {
|
|
184
|
+
author: 'test',
|
|
185
|
+
priority: 'high',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('validates a minimal plan manifest', () => {
|
|
194
|
+
const plan = {
|
|
195
|
+
id: 'my-plan',
|
|
196
|
+
title: 'My Plan',
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
200
|
+
expect(result.success).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('validates empty catalysts array', () => {
|
|
204
|
+
const plan = {
|
|
205
|
+
id: 'my-plan',
|
|
206
|
+
title: 'My Plan',
|
|
207
|
+
catalysts: [],
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
211
|
+
expect(result.success).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('invalid manifests', () => {
|
|
216
|
+
it('rejects missing id', () => {
|
|
217
|
+
const plan = {
|
|
218
|
+
title: 'My Plan',
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
222
|
+
expect(result.success).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('rejects missing title', () => {
|
|
226
|
+
const plan = {
|
|
227
|
+
id: 'my-plan',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
231
|
+
expect(result.success).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('rejects empty id', () => {
|
|
235
|
+
const plan = {
|
|
236
|
+
id: '',
|
|
237
|
+
title: 'My Plan',
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
241
|
+
expect(result.success).toBe(false);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('rejects empty title', () => {
|
|
245
|
+
const plan = {
|
|
246
|
+
id: 'my-plan',
|
|
247
|
+
title: '',
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const result = PlanManifestSchema.safeParse(plan);
|
|
251
|
+
expect(result.success).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe('FacetsDeclarationSchema', () => {
|
|
257
|
+
it('validates all facets declared', () => {
|
|
258
|
+
const facets = {
|
|
259
|
+
questions: true,
|
|
260
|
+
constraints: true,
|
|
261
|
+
outputTemplates: true,
|
|
262
|
+
domainKnowledge: true,
|
|
263
|
+
processGuidance: true,
|
|
264
|
+
validationRules: true,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const result = FacetsDeclarationSchema.safeParse(facets);
|
|
268
|
+
expect(result.success).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('validates partial facets', () => {
|
|
272
|
+
const facets = {
|
|
273
|
+
questions: true,
|
|
274
|
+
constraints: false,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const result = FacetsDeclarationSchema.safeParse(facets);
|
|
278
|
+
expect(result.success).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('validates empty object', () => {
|
|
282
|
+
const facets = {};
|
|
283
|
+
|
|
284
|
+
const result = FacetsDeclarationSchema.safeParse(facets);
|
|
285
|
+
expect(result.success).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe('Constants', () => {
|
|
290
|
+
it('has correct facet directory mappings', () => {
|
|
291
|
+
expect(FACET_DIRECTORIES.questions).toBe('questions');
|
|
292
|
+
expect(FACET_DIRECTORIES.constraints).toBe('constraints');
|
|
293
|
+
expect(FACET_DIRECTORIES.outputTemplates).toBe('output-templates');
|
|
294
|
+
expect(FACET_DIRECTORIES.domainKnowledge).toBe('domain-knowledge');
|
|
295
|
+
expect(FACET_DIRECTORIES.processGuidance).toBe('process-guidance');
|
|
296
|
+
expect(FACET_DIRECTORIES.validationRules).toBe('validation-rules');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('has all six facet types', () => {
|
|
300
|
+
expect(FACET_TYPES).toHaveLength(6);
|
|
301
|
+
expect(FACET_TYPES).toContain('questions');
|
|
302
|
+
expect(FACET_TYPES).toContain('constraints');
|
|
303
|
+
expect(FACET_TYPES).toContain('outputTemplates');
|
|
304
|
+
expect(FACET_TYPES).toContain('domainKnowledge');
|
|
305
|
+
expect(FACET_TYPES).toContain('processGuidance');
|
|
306
|
+
expect(FACET_TYPES).toContain('validationRules');
|
|
307
|
+
});
|
|
308
|
+
});
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
process.env.NODE_OPTIONS = process.env.NODE_OPTIONS || '--experimental-vm-modules';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"allowSyntheticDefaultImports": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": ".",
|
|
11
|
+
"types": ["node", "vitest/globals"],
|
|
12
|
+
"incremental": true,
|
|
13
|
+
"allowJs": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": {
|
|
17
|
+
"@/*": ["src/*"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*", "tests/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist"]
|
|
22
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import replace from '@rollup/plugin-replace';
|
|
3
|
+
import dts from 'vite-plugin-dts';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
build: {
|
|
7
|
+
target: 'esnext',
|
|
8
|
+
outDir: 'dist',
|
|
9
|
+
lib: {
|
|
10
|
+
entry: './src/riotplan-catalyst.ts',
|
|
11
|
+
formats: ['es'],
|
|
12
|
+
fileName: () => 'riotplan-catalyst.js',
|
|
13
|
+
},
|
|
14
|
+
rollupOptions: {
|
|
15
|
+
external: ['zod', 'yaml', /^node:/],
|
|
16
|
+
output: {
|
|
17
|
+
format: 'esm',
|
|
18
|
+
preserveModules: true,
|
|
19
|
+
exports: 'named',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
sourcemap: true,
|
|
23
|
+
},
|
|
24
|
+
plugins: [
|
|
25
|
+
replace({
|
|
26
|
+
preventAssignment: true,
|
|
27
|
+
values: {
|
|
28
|
+
__VERSION__: JSON.stringify(process.env.npm_package_version),
|
|
29
|
+
},
|
|
30
|
+
}),
|
|
31
|
+
dts({
|
|
32
|
+
include: ['src/**/*.ts'],
|
|
33
|
+
exclude: ['src/**/*.test.ts', 'tests/**/*'],
|
|
34
|
+
outDir: 'dist',
|
|
35
|
+
insertTypesEntry: true,
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
resolve: {
|
|
39
|
+
alias: {
|
|
40
|
+
'@': new URL('./src', import.meta.url).pathname,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
environment: 'node',
|
|
6
|
+
setupFiles: ['./tests/setup.ts'],
|
|
7
|
+
coverage: {
|
|
8
|
+
provider: 'v8',
|
|
9
|
+
reporter: ['text', 'json', 'html'],
|
|
10
|
+
exclude: [
|
|
11
|
+
'node_modules/',
|
|
12
|
+
'dist/**',
|
|
13
|
+
'vitest.config.ts',
|
|
14
|
+
'vite.config.ts',
|
|
15
|
+
'eslint.config.mjs',
|
|
16
|
+
'src/loader/**', // placeholder files
|
|
17
|
+
'src/merger/**', // placeholder files
|
|
18
|
+
'src/types.ts', // type definitions
|
|
19
|
+
'src/riotplan-catalyst.ts', // main entry (re-exports)
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
resolve: {
|
|
24
|
+
alias: {
|
|
25
|
+
'@': new URL('./src', import.meta.url).pathname,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|