forgeprint 0.1.1

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.
Files changed (71) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +41 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +10 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/build-codeowners.d.ts +8 -0
  8. package/dist/build-codeowners.d.ts.map +1 -0
  9. package/dist/build-codeowners.js +26 -0
  10. package/dist/build-codeowners.js.map +1 -0
  11. package/dist/build-index.d.ts +45 -0
  12. package/dist/build-index.d.ts.map +1 -0
  13. package/dist/build-index.js +41 -0
  14. package/dist/build-index.js.map +1 -0
  15. package/dist/build-schema.d.ts +4 -0
  16. package/dist/build-schema.d.ts.map +1 -0
  17. package/dist/build-schema.js +38 -0
  18. package/dist/build-schema.js.map +1 -0
  19. package/dist/catalog.d.ts +23 -0
  20. package/dist/catalog.d.ts.map +1 -0
  21. package/dist/catalog.js +55 -0
  22. package/dist/catalog.js.map +1 -0
  23. package/dist/cli.d.ts +8 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +128 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +13 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +30 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/index.d.ts +15 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +15 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/json.d.ts +5 -0
  36. package/dist/json.d.ts.map +1 -0
  37. package/dist/json.js +9 -0
  38. package/dist/json.js.map +1 -0
  39. package/dist/lint-setup.d.ts +19 -0
  40. package/dist/lint-setup.d.ts.map +1 -0
  41. package/dist/lint-setup.js +278 -0
  42. package/dist/lint-setup.js.map +1 -0
  43. package/dist/manifest.d.ts +104 -0
  44. package/dist/manifest.d.ts.map +1 -0
  45. package/dist/manifest.js +93 -0
  46. package/dist/manifest.js.map +1 -0
  47. package/dist/options.d.ts +43 -0
  48. package/dist/options.d.ts.map +1 -0
  49. package/dist/options.js +127 -0
  50. package/dist/options.js.map +1 -0
  51. package/dist/paths.d.ts +16 -0
  52. package/dist/paths.d.ts.map +1 -0
  53. package/dist/paths.js +37 -0
  54. package/dist/paths.js.map +1 -0
  55. package/dist/similarity.d.ts +61 -0
  56. package/dist/similarity.d.ts.map +1 -0
  57. package/dist/similarity.js +184 -0
  58. package/dist/similarity.js.map +1 -0
  59. package/dist/taxonomy.d.ts +24 -0
  60. package/dist/taxonomy.d.ts.map +1 -0
  61. package/dist/taxonomy.js +46 -0
  62. package/dist/taxonomy.js.map +1 -0
  63. package/dist/testing.d.ts +23 -0
  64. package/dist/testing.d.ts.map +1 -0
  65. package/dist/testing.js +107 -0
  66. package/dist/testing.js.map +1 -0
  67. package/dist/validate.d.ts +16 -0
  68. package/dist/validate.d.ts.map +1 -0
  69. package/dist/validate.js +122 -0
  70. package/dist/validate.js.map +1 -0
  71. package/package.json +58 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD"}
package/dist/json.js ADDED
@@ -0,0 +1,9 @@
1
+ /** Serialize a generated artifact: 2-space JSON with a trailing newline. */
2
+ export function stableJson(value) {
3
+ return `${JSON.stringify(value, null, 2)}\n`;
4
+ }
5
+ /** Normalize line endings so drift checks do not fire on checkout differences. */
6
+ export function normalizeNewlines(text) {
7
+ return text.replace(/\r\n/g, '\n');
8
+ }
9
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Structural and safety rules for `setup.md`.
3
+ *
4
+ * A setup recipe is executed by an agent on somebody else's machine, so it is
5
+ * held to a script's standards rather than a document's: numbered steps, one
6
+ * action each, a verification after each one, pinned versions, and no command
7
+ * that reaches outside the project.
8
+ */
9
+ export interface SetupProblem {
10
+ readonly line: number;
11
+ readonly rule: string;
12
+ readonly message: string;
13
+ }
14
+ export interface LintOptions {
15
+ /** Option fields and values declared in the manifest, for guard checking. */
16
+ readonly options?: Readonly<Record<string, readonly string[]>>;
17
+ }
18
+ export declare function lintSetup(source: string, { options }?: LintOptions): SetupProblem[];
19
+ //# sourceMappingURL=lint-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-setup.d.ts","sourceRoot":"","sources":["../src/lint-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AA2HD,MAAM,WAAW,WAAW;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CAChE;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAY,EAAE,GAAE,WAAgB,GAAG,YAAY,EAAE,CA0L5F"}
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Structural and safety rules for `setup.md`.
3
+ *
4
+ * A setup recipe is executed by an agent on somebody else's machine, so it is
5
+ * held to a script's standards rather than a document's: numbered steps, one
6
+ * action each, a verification after each one, pinned versions, and no command
7
+ * that reaches outside the project.
8
+ */
9
+ /** Marker that closes a step: the command that proves the step worked. */
10
+ const VERIFY_PATTERN = /^\s*(?:[-*]\s*)?Verify:\s*`[^`]+`/;
11
+ /** `1. Do the thing` — the start of a step. */
12
+ const STEP_PATTERN = /^(\d+)\.\s+(\S.*)$/;
13
+ const IF_PATTERN = /^<!--\s*if\s+options\.([a-z0-9-]+)\s*==\s*([a-z0-9-]+)\s*-->$/;
14
+ const ENDIF_PATTERN = /^<!--\s*endif\s*-->$/;
15
+ const ANY_GUARD_PATTERN = /^<!--\s*(if|endif)\b/;
16
+ /** Opening or closing line of a fenced code block. */
17
+ const FENCE_PATTERN = /^\s*(?:```|~~~)/;
18
+ /** Hosts a setup step may reach: package registries and the local machine. */
19
+ const ALLOWED_HOSTS = new Set([
20
+ 'localhost',
21
+ '127.0.0.1',
22
+ '[::1]',
23
+ 'api.nuget.org',
24
+ 'nuget.org',
25
+ 'www.nuget.org',
26
+ 'registry.npmjs.org',
27
+ 'www.npmjs.com',
28
+ 'npmjs.com',
29
+ 'pypi.org',
30
+ 'files.pythonhosted.org',
31
+ 'crates.io',
32
+ 'static.crates.io',
33
+ 'proxy.golang.org',
34
+ 'repo.maven.apache.org',
35
+ 'rubygems.org',
36
+ 'packagist.org',
37
+ 'mcr.microsoft.com',
38
+ 'ghcr.io',
39
+ 'docker.io',
40
+ 'registry-1.docker.io',
41
+ ]);
42
+ const FORBIDDEN = [
43
+ {
44
+ rule: 'no-pipe-to-shell',
45
+ pattern: /(?:curl|wget|iwr|invoke-webrequest)[^`\n]*\|[^`\n]*\b(?:ba|z|fi)?sh\b/i,
46
+ message: 'piping a download into a shell is not allowed',
47
+ },
48
+ {
49
+ rule: 'no-sudo',
50
+ pattern: /(?:^|[\s`(])sudo\s/i,
51
+ message: 'sudo is not allowed: a setup step may not need elevated privileges',
52
+ },
53
+ {
54
+ rule: 'no-recursive-delete',
55
+ pattern: /(?:\brm\s+-[a-zA-Z]*r[a-zA-Z]*f|\bRemove-Item\b[^`\n]*-Recurse[^`\n]*-Force)/,
56
+ message: 'recursive force deletes are not allowed',
57
+ },
58
+ {
59
+ rule: 'no-open-permissions',
60
+ pattern: /\bchmod\s+(?:-R\s+)?777\b/,
61
+ message: 'chmod 777 is not allowed',
62
+ },
63
+ {
64
+ rule: 'no-system-paths',
65
+ pattern: /(?:^|[\s`"'(])(?:\/usr\/|\/etc\/|\/opt\/|\/var\/|C:\\Windows|C:\\Program Files)/i,
66
+ message: 'a setup step may not write outside the project directory',
67
+ },
68
+ ];
69
+ const PINNING = [
70
+ {
71
+ rule: 'pin-nuget',
72
+ detect: /\bdotnet\s+add\s+package\s+\S+/i,
73
+ pinned: /--version\s+\S+/i,
74
+ message: 'dotnet add package needs an explicit --version',
75
+ },
76
+ {
77
+ rule: 'pin-npm',
78
+ detect: /\bnpm\s+(?:install|i|add)\s+(?!-)[@\w][^\s`]*/i,
79
+ pinned: /\bnpm\s+(?:install|i|add)\s+(?:-\S+\s+)*@?[^\s`@]+(?:\/[^\s`@]+)?@\S+/i,
80
+ message: 'npm install needs an explicit version, for example package@1.2.3',
81
+ },
82
+ {
83
+ rule: 'pin-pip',
84
+ detect: /\bpip\s+install\s+(?!-)[^\s`]+/i,
85
+ pinned: /\bpip\s+install\s+(?:-\S+\s+)*[^\s`]+==\S+/i,
86
+ message: 'pip install needs an explicit ==version',
87
+ },
88
+ {
89
+ rule: 'pin-image',
90
+ detect: /^\s*FROM\s+\S+/,
91
+ pinned: /^\s*FROM\s+\S+:(?!latest\b)\S+/,
92
+ message: 'a Dockerfile FROM needs an explicit tag that is not "latest"',
93
+ },
94
+ {
95
+ rule: 'pin-image',
96
+ detect: /\bdocker\s+(?:run|pull)\b/i,
97
+ // An image reference starts with a letter, which is what separates it from
98
+ // a port mapping such as `-p 8080:8080`.
99
+ pinned: /\bdocker\s+(?:run|pull)\b[^`\n]*\s[a-z][\w./-]*:(?!latest\b)[\w.-]+/i,
100
+ message: 'container images need an explicit tag that is not "latest"',
101
+ },
102
+ ];
103
+ export function lintSetup(source, { options = {} } = {}) {
104
+ const problems = [];
105
+ const lines = source.split(/\r?\n/);
106
+ const add = (line, rule, message) => {
107
+ problems.push({ line, rule, message });
108
+ };
109
+ const steps = [];
110
+ let current = null;
111
+ // Guard state: at most one level, because a nested branch is a variant in
112
+ // disguise (ADR 0001).
113
+ // Held on an object so that narrowing does not leak out of the loop below.
114
+ const guardState = {
115
+ open: null,
116
+ };
117
+ let expected = 1;
118
+ let guardEntry = 1;
119
+ let afterGuard = null;
120
+ /** The option field of the guard group being read, and the values it used. */
121
+ let groupField = null;
122
+ let groupValues = new Set();
123
+ const closeStep = () => {
124
+ if (current !== null)
125
+ steps.push(current);
126
+ current = null;
127
+ };
128
+ // A fenced block is content, not structure: a line inside one that looks like
129
+ // a step or a guard is neither.
130
+ let inFence = false;
131
+ lines.forEach((text, index) => {
132
+ const line = index + 1;
133
+ const isFence = FENCE_PATTERN.test(text);
134
+ if (isFence)
135
+ inFence = !inFence;
136
+ if (inFence || isFence) {
137
+ if (current !== null)
138
+ current.body.push(text);
139
+ return;
140
+ }
141
+ const guard = IF_PATTERN.exec(text.trim());
142
+ const isEndif = ENDIF_PATTERN.test(text.trim());
143
+ if (guard !== null) {
144
+ closeStep();
145
+ if (guardState.open !== null) {
146
+ add(line, 'no-nested-guards', `guard opened at line ${guardState.open.line} is still open`);
147
+ }
148
+ const [, field = '', value = ''] = guard;
149
+ const declared = options[field];
150
+ if (declared === undefined) {
151
+ add(line, 'unknown-option', `options.${field} is not declared in manifest.yaml`);
152
+ }
153
+ else if (!declared.includes(value)) {
154
+ add(line, 'unknown-option-value', `"${value}" is not a declared value of options.${field} (${declared.join(', ')})`);
155
+ }
156
+ // A sibling branch of the same group repeats its numbering instead of
157
+ // continuing it. A repeated value means a new group, not a sibling.
158
+ if (afterGuard !== null && field === groupField && !groupValues.has(value)) {
159
+ expected = guardEntry;
160
+ groupValues.add(value);
161
+ }
162
+ else {
163
+ if (afterGuard !== null) {
164
+ expected = afterGuard;
165
+ afterGuard = null;
166
+ }
167
+ guardEntry = expected;
168
+ groupField = field;
169
+ groupValues = new Set([value]);
170
+ }
171
+ guardState.open = { field, value, line };
172
+ return;
173
+ }
174
+ if (isEndif) {
175
+ closeStep();
176
+ if (guardState.open === null) {
177
+ add(line, 'unmatched-endif', 'endif without a matching if');
178
+ }
179
+ else {
180
+ afterGuard = Math.max(afterGuard ?? 0, expected);
181
+ expected = guardEntry;
182
+ guardState.open = null;
183
+ }
184
+ return;
185
+ }
186
+ if (ANY_GUARD_PATTERN.test(text.trim())) {
187
+ add(line, 'guard-syntax', 'a guard must read exactly `<!-- if options.<field> == <value> -->` or `<!-- endif -->`');
188
+ return;
189
+ }
190
+ const step = STEP_PATTERN.exec(text);
191
+ if (step !== null) {
192
+ closeStep();
193
+ const number = Number(step[1]);
194
+ if (guardState.open === null && afterGuard !== null) {
195
+ expected = afterGuard;
196
+ afterGuard = null;
197
+ }
198
+ if (number !== expected) {
199
+ add(line, 'step-numbering', `step is numbered ${number}, expected ${expected}${guardState.open === null
200
+ ? ''
201
+ : ' (a guarded branch continues the numbering it opened at)'}`);
202
+ expected = number;
203
+ }
204
+ expected += 1;
205
+ current = { number, line, body: [step[2] ?? ''] };
206
+ return;
207
+ }
208
+ if (current !== null && text.trim().length > 0) {
209
+ current.body.push(text);
210
+ }
211
+ else if (current !== null && text.trim().length === 0) {
212
+ current.body.push('');
213
+ }
214
+ });
215
+ closeStep();
216
+ if (guardState.open !== null) {
217
+ add(guardState.open.line, 'unclosed-guard', 'guard is never closed by `<!-- endif -->`');
218
+ }
219
+ if (steps.length === 0) {
220
+ add(1, 'no-steps', 'setup.md has no numbered steps');
221
+ }
222
+ for (const step of steps) {
223
+ // The verification line does not count: the action itself must name a
224
+ // command or a file, or the step is prose telling someone to figure it out.
225
+ const action = step.body.filter((text) => !VERIFY_PATTERN.test(text)).join('\n');
226
+ if (!/`[^`]+`/.test(action)) {
227
+ add(step.line, 'step-needs-command', `step ${step.number} states no command or file path; a step is one concrete action`);
228
+ }
229
+ if (!step.body.some((text) => VERIFY_PATTERN.test(text))) {
230
+ add(step.line, 'step-needs-verification', `step ${step.number} has no "Verify: \`command\`" line`);
231
+ }
232
+ }
233
+ // Only commands are linted. Prose that mentions `sudo` to warn against it is
234
+ // not a violation; a code span that runs it is.
235
+ let inCodeFence = false;
236
+ lines.forEach((text, index) => {
237
+ const line = index + 1;
238
+ if (FENCE_PATTERN.test(text)) {
239
+ inCodeFence = !inCodeFence;
240
+ return;
241
+ }
242
+ const code = inCodeFence ? text : inlineCode(text);
243
+ if (code.trim().length === 0)
244
+ return;
245
+ for (const { rule, pattern, message } of FORBIDDEN) {
246
+ if (pattern.test(code))
247
+ add(line, rule, message);
248
+ }
249
+ for (const { rule, detect, pinned, message } of PINNING) {
250
+ if (detect.test(code) && !pinned.test(code))
251
+ add(line, rule, message);
252
+ }
253
+ for (const host of hostsIn(code)) {
254
+ if (!ALLOWED_HOSTS.has(host)) {
255
+ add(line, 'registry-only', `${host} is not a package registry or the local machine`);
256
+ }
257
+ }
258
+ });
259
+ return problems.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
260
+ }
261
+ /** The contents of every `code span` on a line, joined by newlines. */
262
+ function inlineCode(text) {
263
+ return [...text.matchAll(/`([^`]+)`/g)].map((match) => match[1] ?? '').join('\n');
264
+ }
265
+ /** Hosts of every http(s) URL on a line. */
266
+ function hostsIn(text) {
267
+ const hosts = [];
268
+ const urls = text.matchAll(/https?:\/\/([^\s/`)"'>]+)/gi);
269
+ for (const match of urls) {
270
+ const authority = match[1];
271
+ if (authority === undefined)
272
+ continue;
273
+ const host = authority.split('@').pop() ?? authority;
274
+ hosts.push(host.replace(/:\d+$/, '').toLowerCase());
275
+ }
276
+ return hosts;
277
+ }
278
+ //# sourceMappingURL=lint-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint-setup.js","sourceRoot":"","sources":["../src/lint-setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import { z } from 'zod';
2
+ import { type Taxonomy } from './taxonomy.js';
3
+ /** ADR 0001: variation is limited so the option matrix stays testable. */
4
+ export declare const MAX_OPTION_FIELDS = 3;
5
+ export declare const MAX_OPTION_VALUES = 3;
6
+ export declare const MIN_OPTION_VALUES = 2;
7
+ /**
8
+ * The manifest shape without cross-field rules, so that it can be rendered as
9
+ * JSON Schema. `manifestSchema` adds the rules that JSON Schema cannot express.
10
+ */
11
+ export declare function manifestObjectSchema(taxonomy: Taxonomy): z.ZodObject<{
12
+ schema: z.ZodLiteral<1>;
13
+ slug: z.ZodString;
14
+ name: z.ZodString;
15
+ version: z.ZodString;
16
+ tier: z.ZodEnum<{
17
+ [x: string]: string;
18
+ }>;
19
+ maintainers: z.ZodArray<z.ZodString>;
20
+ summary: z.ZodString;
21
+ stack: z.ZodArray<z.ZodEnum<{
22
+ [x: string]: string;
23
+ }>>;
24
+ languages: z.ZodArray<z.ZodEnum<{
25
+ [x: string]: string;
26
+ }>>;
27
+ platforms: z.ZodArray<z.ZodEnum<{
28
+ [x: string]: string;
29
+ }>>;
30
+ distribution: z.ZodArray<z.ZodEnum<{
31
+ [x: string]: string;
32
+ }>>;
33
+ project_type: z.ZodEnum<{
34
+ [x: string]: string;
35
+ }>;
36
+ audience: z.ZodArray<z.ZodEnum<{
37
+ [x: string]: string;
38
+ }>>;
39
+ requirements: z.ZodArray<z.ZodEnum<{
40
+ [x: string]: string;
41
+ }>>;
42
+ agents: z.ZodArray<z.ZodEnum<{
43
+ [x: string]: string;
44
+ }>>;
45
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
46
+ provides: z.ZodOptional<z.ZodObject<{
47
+ mcp: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
49
+ }, z.core.$strict>>;
50
+ requires_tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
51
+ deprecated: z.ZodDefault<z.ZodBoolean>;
52
+ supersedes: z.ZodDefault<z.ZodNullable<z.ZodString>>;
53
+ }, z.core.$strict>;
54
+ /** Runtime validation schema: the object shape plus cross-field rules. */
55
+ export declare function manifestSchema(taxonomy: Taxonomy): z.ZodObject<{
56
+ schema: z.ZodLiteral<1>;
57
+ slug: z.ZodString;
58
+ name: z.ZodString;
59
+ version: z.ZodString;
60
+ tier: z.ZodEnum<{
61
+ [x: string]: string;
62
+ }>;
63
+ maintainers: z.ZodArray<z.ZodString>;
64
+ summary: z.ZodString;
65
+ stack: z.ZodArray<z.ZodEnum<{
66
+ [x: string]: string;
67
+ }>>;
68
+ languages: z.ZodArray<z.ZodEnum<{
69
+ [x: string]: string;
70
+ }>>;
71
+ platforms: z.ZodArray<z.ZodEnum<{
72
+ [x: string]: string;
73
+ }>>;
74
+ distribution: z.ZodArray<z.ZodEnum<{
75
+ [x: string]: string;
76
+ }>>;
77
+ project_type: z.ZodEnum<{
78
+ [x: string]: string;
79
+ }>;
80
+ audience: z.ZodArray<z.ZodEnum<{
81
+ [x: string]: string;
82
+ }>>;
83
+ requirements: z.ZodArray<z.ZodEnum<{
84
+ [x: string]: string;
85
+ }>>;
86
+ agents: z.ZodArray<z.ZodEnum<{
87
+ [x: string]: string;
88
+ }>>;
89
+ options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
90
+ provides: z.ZodOptional<z.ZodObject<{
91
+ mcp: z.ZodOptional<z.ZodArray<z.ZodString>>;
92
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
93
+ }, z.core.$strict>>;
94
+ requires_tools: z.ZodOptional<z.ZodArray<z.ZodString>>;
95
+ deprecated: z.ZodDefault<z.ZodBoolean>;
96
+ supersedes: z.ZodDefault<z.ZodNullable<z.ZodString>>;
97
+ }, z.core.$strict>;
98
+ export type Manifest = z.infer<ReturnType<typeof manifestObjectSchema>>;
99
+ /**
100
+ * The combination that must be unique across the catalog (rule 9): two
101
+ * blueprints may not claim the same stack, project type and requirements.
102
+ */
103
+ export declare function combinationKey(manifest: Manifest): string;
104
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAanE,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAiBnC;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAmCtD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAkBhD;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC;AAExE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAIzD"}
@@ -0,0 +1,93 @@
1
+ import { z } from 'zod';
2
+ import { SLUG_PATTERN, terms } from './taxonomy.js';
3
+ /** https://semver.org — the official recommended pattern, anchored. */
4
+ const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
5
+ /** GitHub login, as GitHub itself validates it. */
6
+ const GITHUB_HANDLE_PATTERN = /^[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}$/;
7
+ /** A tool with an optional version constraint, for example `dotnet>=9`. */
8
+ const REQUIRED_TOOL_PATTERN = /^[a-z0-9][a-z0-9.+-]*(?:\s*(?:>=|<=|==|>|<|~|\^)\s*[0-9][0-9a-zA-Z.-]*)?$/;
9
+ /** ADR 0001: variation is limited so the option matrix stays testable. */
10
+ export const MAX_OPTION_FIELDS = 3;
11
+ export const MAX_OPTION_VALUES = 3;
12
+ export const MIN_OPTION_VALUES = 2;
13
+ const slug = z.string().regex(SLUG_PATTERN, 'must be lower kebab-case');
14
+ function vocabulary(taxonomy, name) {
15
+ const values = terms(taxonomy, name);
16
+ if (values.length === 0)
17
+ throw new Error(`Taxonomy vocabulary "${name}" is empty`);
18
+ return z.enum(values);
19
+ }
20
+ function uniqueArray(item, min = 1) {
21
+ return z
22
+ .array(item)
23
+ .min(min)
24
+ .refine((values) => new Set(values).size === values.length, 'must not contain duplicates');
25
+ }
26
+ /**
27
+ * The manifest shape without cross-field rules, so that it can be rendered as
28
+ * JSON Schema. `manifestSchema` adds the rules that JSON Schema cannot express.
29
+ */
30
+ export function manifestObjectSchema(taxonomy) {
31
+ return z
32
+ .object({
33
+ schema: z.literal(1),
34
+ slug,
35
+ name: z.string().min(3).max(80),
36
+ version: z.string().regex(SEMVER_PATTERN, 'must be a semantic version'),
37
+ tier: vocabulary(taxonomy, 'tier'),
38
+ maintainers: uniqueArray(z.string().regex(GITHUB_HANDLE_PATTERN, 'must be a GitHub login')),
39
+ summary: z.string().min(20).max(200),
40
+ stack: uniqueArray(vocabulary(taxonomy, 'stack')),
41
+ languages: uniqueArray(vocabulary(taxonomy, 'languages')),
42
+ platforms: uniqueArray(vocabulary(taxonomy, 'platforms')),
43
+ distribution: uniqueArray(vocabulary(taxonomy, 'distribution')),
44
+ project_type: vocabulary(taxonomy, 'project_type'),
45
+ audience: uniqueArray(vocabulary(taxonomy, 'audience')),
46
+ requirements: uniqueArray(vocabulary(taxonomy, 'requirements')),
47
+ agents: uniqueArray(vocabulary(taxonomy, 'agents')),
48
+ options: z
49
+ .record(slug, uniqueArray(slug, MIN_OPTION_VALUES).max(MAX_OPTION_VALUES))
50
+ .optional(),
51
+ provides: z
52
+ .object({
53
+ mcp: uniqueArray(slug).optional(),
54
+ skills: uniqueArray(slug).optional(),
55
+ })
56
+ .strict()
57
+ .optional(),
58
+ requires_tools: uniqueArray(z.string().regex(REQUIRED_TOOL_PATTERN, 'must be a tool name with an optional version')).optional(),
59
+ deprecated: z.boolean().default(false),
60
+ supersedes: slug.nullable().default(null),
61
+ })
62
+ .strict();
63
+ }
64
+ /** Runtime validation schema: the object shape plus cross-field rules. */
65
+ export function manifestSchema(taxonomy) {
66
+ return manifestObjectSchema(taxonomy).superRefine((manifest, ctx) => {
67
+ const fields = Object.keys(manifest.options ?? {});
68
+ if (fields.length > MAX_OPTION_FIELDS) {
69
+ ctx.addIssue({
70
+ code: 'custom',
71
+ path: ['options'],
72
+ message: `at most ${MAX_OPTION_FIELDS} option fields are allowed, found ${fields.length} (ADR 0001)`,
73
+ });
74
+ }
75
+ if (manifest.supersedes === manifest.slug) {
76
+ ctx.addIssue({
77
+ code: 'custom',
78
+ path: ['supersedes'],
79
+ message: 'a blueprint cannot supersede itself',
80
+ });
81
+ }
82
+ });
83
+ }
84
+ /**
85
+ * The combination that must be unique across the catalog (rule 9): two
86
+ * blueprints may not claim the same stack, project type and requirements.
87
+ */
88
+ export function combinationKey(manifest) {
89
+ const stack = [...manifest.stack].sort().join(',');
90
+ const requirements = [...manifest.requirements].sort().join(',');
91
+ return `${stack}|${manifest.project_type}|${requirements}`;
92
+ }
93
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,KAAK,EAAiB,MAAM,eAAe,CAAC;AAEnE,uEAAuE;AACvE,MAAM,cAAc,GAClB,qLAAqL,CAAC;AAExL,mDAAmD;AACnD,MAAM,qBAAqB,GAAG,kDAAkD,CAAC;AAEjF,2EAA2E;AAC3E,MAAM,qBAAqB,GACzB,2EAA2E,CAAC;AAE9E,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AACnC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAEnC,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,0BAA0B,CAAC,CAAC;AAExE,SAAS,UAAU,CAAC,QAAkB,EAAE,IAAiC;IACvE,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,YAAY,CAAC,CAAC;IACnF,OAAO,CAAC,CAAC,IAAI,CAAC,MAA+B,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,WAAW,CAAsB,IAAO,EAAE,GAAG,GAAG,CAAC;IACxD,OAAO,CAAC;SACL,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,GAAG,CAAC;SACR,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAAC;AAC/F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAkB;IACrD,OAAO,CAAC;SACL,MAAM,CAAC;QACN,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;QACpB,IAAI;QACJ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,4BAA4B,CAAC;QACvE,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC;QAClC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,qBAAqB,EAAE,wBAAwB,CAAC,CAAC;QAC3F,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;QACpC,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACzD,SAAS,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QACzD,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAC/D,YAAY,EAAE,UAAU,CAAC,QAAQ,EAAE,cAAc,CAAC;QAClD,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACvD,YAAY,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAC/D,MAAM,EAAE,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACnD,OAAO,EAAE,CAAC;aACP,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;aACzE,QAAQ,EAAE;QACb,QAAQ,EAAE,CAAC;aACR,MAAM,CAAC;YACN,GAAG,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;YACjC,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;SACrC,CAAC;aACD,MAAM,EAAE;aACR,QAAQ,EAAE;QACb,cAAc,EAAE,WAAW,CACzB,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,qBAAqB,EAAE,8CAA8C,CAAC,CACxF,CAAC,QAAQ,EAAE;QACZ,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QACtC,UAAU,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;KAC1C,CAAC;SACD,MAAM,EAAE,CAAC;AACd,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC/C,OAAO,oBAAoB,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;QAClE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QACnD,IAAI,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACtC,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,SAAS,CAAC;gBACjB,OAAO,EAAE,WAAW,iBAAiB,qCAAqC,MAAM,CAAC,MAAM,aAAa;aACrG,CAAC,CAAC;QACL,CAAC;QACD,IAAI,QAAQ,CAAC,UAAU,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC1C,GAAG,CAAC,QAAQ,CAAC;gBACX,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,YAAY,CAAC;gBACpB,OAAO,EAAE,qCAAqC;aAC/C,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAID;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAkB;IAC/C,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,CAAC,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAAC,YAAY,IAAI,YAAY,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Resolving `setup.md` option guards.
3
+ *
4
+ * A recipe is written with every branch present:
5
+ *
6
+ * ```markdown
7
+ * <!-- if options.database == postgres -->
8
+ * ...
9
+ * <!-- endif -->
10
+ * ```
11
+ *
12
+ * A caller who has chosen `database: postgres` should receive the recipe with
13
+ * that branch inlined and the others gone, because a step list with branches in
14
+ * it is a document, and the point of `setup.md` is that it is a script.
15
+ */
16
+ import type { Manifest } from './manifest.js';
17
+ export interface ResolveOptionsResult {
18
+ readonly markdown: string;
19
+ /** Option fields that appear in guards but were not chosen by the caller. */
20
+ readonly unresolved: readonly string[];
21
+ }
22
+ export declare class UnknownOptionError extends Error {
23
+ }
24
+ /**
25
+ * Validate a caller's option selection against what the manifest declares.
26
+ * Throws rather than guessing: a typo that silently selects the default would
27
+ * hand somebody a recipe for a database they did not ask for.
28
+ */
29
+ export declare function checkOptions(manifest: Manifest, chosen: Readonly<Record<string, string>>): void;
30
+ /**
31
+ * Inline the chosen branches and drop the rest.
32
+ *
33
+ * A guard on a field the caller did not choose is left exactly as written, and
34
+ * the field is reported in `unresolved`, so the caller can be asked rather than
35
+ * handed a recipe with a branch silently removed.
36
+ */
37
+ export declare function resolveSetupOptions(markdown: string, chosen?: Readonly<Record<string, string>>): ResolveOptionsResult;
38
+ /** Option fields a caller still has to choose, given what they have chosen. */
39
+ export declare function missingOptions(manifest: Manifest, chosen?: Readonly<Record<string, string>>): {
40
+ field: string;
41
+ values: readonly string[];
42
+ }[];
43
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAK9C,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAEhD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI,CAkB/F;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAC5C,oBAAoB,CAuDtB;AAmBD,+EAA+E;AAC/E,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,QAAQ,EAClB,MAAM,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM,GAC5C;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,CAIhD"}