@oddkin/analyzer 0.0.0-stage → 1.0.0-beta.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -2
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +65 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/contracts.d.ts +29 -0
  8. package/dist/contracts.d.ts.map +1 -0
  9. package/dist/contracts.js +57 -0
  10. package/dist/contracts.js.map +1 -0
  11. package/dist/docs.d.ts +34 -0
  12. package/dist/docs.d.ts.map +1 -0
  13. package/dist/docs.js +63 -0
  14. package/dist/docs.js.map +1 -0
  15. package/dist/emit.d.ts +38 -0
  16. package/dist/emit.d.ts.map +1 -0
  17. package/dist/emit.js +406 -0
  18. package/dist/emit.js.map +1 -0
  19. package/dist/examples.d.ts +35 -0
  20. package/dist/examples.d.ts.map +1 -0
  21. package/dist/examples.js +345 -0
  22. package/dist/examples.js.map +1 -0
  23. package/dist/extract.d.ts +208 -0
  24. package/dist/extract.d.ts.map +1 -0
  25. package/dist/extract.js +1570 -0
  26. package/dist/extract.js.map +1 -0
  27. package/dist/foundations.d.ts +36 -0
  28. package/dist/foundations.d.ts.map +1 -0
  29. package/dist/foundations.js +108 -0
  30. package/dist/foundations.js.map +1 -0
  31. package/dist/history.d.ts +10 -0
  32. package/dist/history.d.ts.map +1 -0
  33. package/dist/history.js +62 -0
  34. package/dist/history.js.map +1 -0
  35. package/dist/impact-bin.d.ts +3 -0
  36. package/dist/impact-bin.d.ts.map +1 -0
  37. package/dist/impact-bin.js +7 -0
  38. package/dist/impact-bin.js.map +1 -0
  39. package/dist/impact-cli.d.ts +8 -0
  40. package/dist/impact-cli.d.ts.map +1 -0
  41. package/dist/impact-cli.js +121 -0
  42. package/dist/impact-cli.js.map +1 -0
  43. package/dist/impact.d.ts +282 -0
  44. package/dist/impact.d.ts.map +1 -0
  45. package/dist/impact.js +744 -0
  46. package/dist/impact.js.map +1 -0
  47. package/dist/index.d.ts +82 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +228 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/kit/agents-md.d.ts +10 -0
  52. package/dist/kit/agents-md.d.ts.map +1 -0
  53. package/dist/kit/agents-md.js +64 -0
  54. package/dist/kit/agents-md.js.map +1 -0
  55. package/dist/kit/cli.d.ts +8 -0
  56. package/dist/kit/cli.d.ts.map +1 -0
  57. package/dist/kit/cli.js +214 -0
  58. package/dist/kit/cli.js.map +1 -0
  59. package/dist/kit/context.d.ts +104 -0
  60. package/dist/kit/context.d.ts.map +1 -0
  61. package/dist/kit/context.js +323 -0
  62. package/dist/kit/context.js.map +1 -0
  63. package/dist/kit/index.d.ts +12 -0
  64. package/dist/kit/index.d.ts.map +1 -0
  65. package/dist/kit/index.js +12 -0
  66. package/dist/kit/index.js.map +1 -0
  67. package/dist/kit/kit.d.ts +62 -0
  68. package/dist/kit/kit.d.ts.map +1 -0
  69. package/dist/kit/kit.js +626 -0
  70. package/dist/kit/kit.js.map +1 -0
  71. package/dist/kit/llms.d.ts +22 -0
  72. package/dist/kit/llms.d.ts.map +1 -0
  73. package/dist/kit/llms.js +58 -0
  74. package/dist/kit/llms.js.map +1 -0
  75. package/dist/kit/package-kit.d.ts +18 -0
  76. package/dist/kit/package-kit.d.ts.map +1 -0
  77. package/dist/kit/package-kit.js +224 -0
  78. package/dist/kit/package-kit.js.map +1 -0
  79. package/dist/kit-bin.d.ts +3 -0
  80. package/dist/kit-bin.d.ts.map +1 -0
  81. package/dist/kit-bin.js +7 -0
  82. package/dist/kit-bin.js.map +1 -0
  83. package/dist/published.d.ts +43 -0
  84. package/dist/published.d.ts.map +1 -0
  85. package/dist/published.js +62 -0
  86. package/dist/published.js.map +1 -0
  87. package/dist/references.d.ts +70 -0
  88. package/dist/references.d.ts.map +1 -0
  89. package/dist/references.js +255 -0
  90. package/dist/references.js.map +1 -0
  91. package/dist/style-mappings.d.ts +46 -0
  92. package/dist/style-mappings.d.ts.map +1 -0
  93. package/dist/style-mappings.js +246 -0
  94. package/dist/style-mappings.js.map +1 -0
  95. package/dist/stylesheets.d.ts +56 -0
  96. package/dist/stylesheets.d.ts.map +1 -0
  97. package/dist/stylesheets.js +83 -0
  98. package/dist/stylesheets.js.map +1 -0
  99. package/dist/verification.d.ts +62 -0
  100. package/dist/verification.d.ts.map +1 -0
  101. package/dist/verification.js +115 -0
  102. package/dist/verification.js.map +1 -0
  103. package/package.json +45 -4
@@ -0,0 +1,626 @@
1
+ /**
2
+ * Agent kits, generation (agent-tooling.md §5): an entry guide, references, and JSON contract
3
+ * bundles for one library, generated from its published metadata and its theme's token
4
+ * inspection, with a freshness check. Exact API facts are extracted; guidance is shown only where
5
+ * the library authored it.
6
+ */
7
+ import { createHash } from 'node:crypto';
8
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
9
+ import { dirname, join, relative, resolve } from 'node:path';
10
+ import { exportTarget, inputsHash, } from './context.js';
11
+ export const KIT_SCHEMA = 'https://oddkin.dev/schemas/agent-kit/v1.json';
12
+ export const GUIDE_BUDGET = 8192;
13
+ const INPUTS_FILE = 'inputs.json';
14
+ export const kitDirName = (name) => name.replace(/^@/, '').replace(/\//g, '-');
15
+ export const refreshCommand = (name, out) => `npx oddkin-kit --package ${name} --out ${out}`;
16
+ const cell = (value) => String(value ?? '')
17
+ .replace(/\|/g, '\\|')
18
+ .replace(/\s*\n\s*/g, ' ');
19
+ const code = (value) => `\`${String(value).replace(/`/g, "'")}\``;
20
+ const table = (head, rows) => rows.length
21
+ ? [
22
+ `| ${head.join(' | ')} |`,
23
+ `| ${head.map(() => '---').join(' | ')} |`,
24
+ ...rows.map((r) => `| ${r.map(cell).join(' | ')} |`),
25
+ '',
26
+ ].join('\n')
27
+ : '';
28
+ const at = (fact) => (fact.source ? ` (${fact.source.file}:${fact.source.line})` : '');
29
+ const posix = (path) => path.split('\\').join('/');
30
+ /** The input files one library's kit depends on: its package, the theme, and the workspace manifest. */
31
+ function kitInputs(project, library) {
32
+ const rel = (file) => posix(relative(project.root, file));
33
+ const prefix = `${rel(library.dir)}/`;
34
+ const shared = new Set([
35
+ 'package.json',
36
+ ...(project.inspection ? [rel(project.inspection.file)] : []),
37
+ ]);
38
+ if (project.context.theme?.config)
39
+ shared.add(project.context.theme.config);
40
+ return new Map([...project.inputs].filter(([path]) => path.startsWith(prefix) || shared.has(path) || /^oddkin\.docs\./.test(path)));
41
+ }
42
+ /**
43
+ * A packaged kit's inputs: the files its package publishes, by path in the package, and the
44
+ * theme's token inspection, by its package export. The same in the workspace that builds the
45
+ * package and in a project that installs it, so the installed package can regenerate its kit.
46
+ */
47
+ function packagedInputs(project, library) {
48
+ const files = library.manifest['files'] ?? [];
49
+ const published = (path) => path === 'package.json' ||
50
+ files.some((entry) => {
51
+ const name = entry.replace(/^\.\//, '').replace(/\/$/, '');
52
+ return path === name || path.startsWith(`${name}/`);
53
+ });
54
+ const inputs = new Map();
55
+ for (const [path, hash] of project.inputs) {
56
+ const inPackage = posix(relative(library.dir, resolve(project.root, path)));
57
+ if (!inPackage.startsWith('../') && !inPackage.startsWith('agent/') && published(inPackage))
58
+ inputs.set(inPackage, hash);
59
+ }
60
+ const inspection = project.inspection;
61
+ if (inspection?.source) {
62
+ const hash = project.inputs.get(posix(relative(project.root, inspection.file)));
63
+ if (hash)
64
+ inputs.set(`${inspection.source.package}${inspection.source.export.slice(1)}`, hash);
65
+ }
66
+ return inputs;
67
+ }
68
+ function fingerprintOf(project, library, inputs) {
69
+ const versions = { [library.name]: library.version };
70
+ for (const dep of Object.keys({
71
+ ...library.manifest['dependencies'],
72
+ ...library.manifest['peerDependencies'],
73
+ }))
74
+ for (const p of project.context.packages)
75
+ if (p.name === dep)
76
+ versions[p.name] = p.version;
77
+ return {
78
+ generator: { ...project.context.fingerprint.generator },
79
+ schemaVersion: 1,
80
+ packages: versions,
81
+ inputs: inputsHash(inputs),
82
+ };
83
+ }
84
+ /** SHA-256 of a library's published metadata file, as packed. */
85
+ export const metadataHash = (dir) => createHash('sha256')
86
+ .update(readFileSync(join(dir, 'oddkin.metadata.json')))
87
+ .digest('hex');
88
+ const declarationOf = (cem, component) => cem?.modules
89
+ ?.find((m) => m.path === component.cem.module)
90
+ ?.declarations?.find((d) => d.name === component.cem.name);
91
+ /** The shortest public specifier whose module exports the class (not a registration entry). */
92
+ function classImport(library, component) {
93
+ const exporting = new Set((library.cem?.['modules'] ?? [])
94
+ .filter((m) => m.exports?.some((e) => e.kind === 'js' && e.name === component.cem.name))
95
+ .map((m) => m.path));
96
+ const specifiers = Object.keys(library.manifest['exports'] ?? {})
97
+ .filter((subpath) => exporting.has(exportTarget(library.manifest, subpath)))
98
+ .map((subpath) => (subpath === '.' ? library.name : `${library.name}${subpath.slice(1)}`))
99
+ .sort((a, b) => a.length - b.length || (a < b ? -1 : 1));
100
+ return specifiers[0];
101
+ }
102
+ function componentReference(library, component, categories, examples) {
103
+ const declaration = declarationOf(library.cem, component) ?? {};
104
+ const tag = component.tag;
105
+ const propertyOnly = new Set((component.propertyOnly ?? []).map((p) => p.property.split('#')[1]));
106
+ const intents = new Map((component.events ?? []).map((e) => [
107
+ e.event.split('#')[1],
108
+ e.intent.known === false ? 'unknown (not declared)' : `${e.intent.value} (authored)`,
109
+ ]));
110
+ const fields = (declaration.members ?? []).filter((m) => m.kind === 'field');
111
+ const methods = (declaration.members ?? []).filter((m) => m.kind === 'method');
112
+ const imported = classImport(library, component);
113
+ const category = component.category ? categories[component.category]?.label : undefined;
114
+ const lines = [
115
+ `# ${code(`<${tag}>`)}${declaration.name ? ` (${declaration.name})` : ''}`,
116
+ '',
117
+ `Package ${code(library.name)} ${library.version}${category ? ` · category ${category}` : ''}. Generated from \`custom-elements.json\` and \`oddkin.metadata.json\`; facts are extracted unless marked authored or verified.`,
118
+ '',
119
+ ...(declaration.description ? [declaration.description, ''] : []),
120
+ '## Import and register',
121
+ '',
122
+ `- Register: ${code(`import '${component.registration}';`)}`,
123
+ imported
124
+ ? `- Class: ${code(`import { ${component.cem.name} } from '${imported}';`)}`
125
+ : '- Class: no public export of the class was found.',
126
+ ...Object.entries(component.definition?.dependencies ?? {}).map(([role, dep]) => `- Dependency (${role}): ${code(`<${dep.replace(/^component:/, '')}>`)}`),
127
+ ...(component.definition?.extends
128
+ ? [
129
+ `- Recomposed from ${code(`<${component.definition.extends.replace(/^component:/, '')}>`)}`,
130
+ ]
131
+ : []),
132
+ '',
133
+ '## Properties',
134
+ '',
135
+ table(['Property', 'Attribute', 'Type', 'Default', 'Description'], fields.map((f) => [
136
+ code(f.name),
137
+ propertyOnly.has(f.name)
138
+ ? 'none: assign as a property'
139
+ : f.attribute
140
+ ? `${code(f.attribute)}${f.reflects ? ' (reflects)' : ''}`
141
+ : f.readonly
142
+ ? 'none (read-only)'
143
+ : 'none',
144
+ f.type?.text ?? '',
145
+ f.default ?? '',
146
+ f.description ?? '',
147
+ ])) || 'None.\n',
148
+ ...(methods.length
149
+ ? [
150
+ '## Methods',
151
+ '',
152
+ table(['Method', 'Description'], methods.map((m) => [code(`${m.name}()`), m.description ?? ''])),
153
+ ]
154
+ : []),
155
+ '## Events',
156
+ '',
157
+ table(['Event', 'Type', 'Intent', 'Description'], (declaration.events ?? []).map((e) => [
158
+ code(e.name),
159
+ e.type?.text ?? '',
160
+ intents.get(e.name) ?? 'unknown (not declared)',
161
+ e.description ?? '',
162
+ ])) || 'None.\n',
163
+ ];
164
+ for (const [title, key] of [
165
+ ['Slots', 'slots'],
166
+ ['Parts', 'cssParts'],
167
+ ['CSS custom properties', 'cssProperties'],
168
+ ['Custom states', 'cssStates'],
169
+ ]) {
170
+ const entries = declaration[key] ?? [];
171
+ lines.push(`## ${title}`, '', table(['Name', 'Description'], entries.map((s) => [s.name ? code(s.name) : '(default)', s.description ?? ''])) || 'None.\n');
172
+ }
173
+ lines.push('## Guidance (authored)', '');
174
+ let authored = 0;
175
+ for (const [label, key] of [
176
+ ['Use for', 'useFor'],
177
+ ['Not for', 'notFor'],
178
+ ['Accessibility', 'a11y'],
179
+ ['Keyboard', 'keyboard'],
180
+ ['Limitations', 'limitations'],
181
+ ])
182
+ for (const fact of component[key] ?? []) {
183
+ authored++;
184
+ lines.push(`- ${label}: ${fact.value}${at(fact)}`);
185
+ }
186
+ if (!authored)
187
+ lines.push('Not authored for this component.');
188
+ lines.push('', '## Examples', '');
189
+ const own = examples.filter(([, e]) => e.component === `component:${tag}`);
190
+ const correct = own.filter(([, e]) => !e.incorrect);
191
+ const mistakes = own.filter(([, e]) => e.incorrect);
192
+ lines.push(...(correct.length
193
+ ? correct.map(([id, e]) => `- ${code(id)} in ${e.file} (package source)${e.previewRoles ? `, preview roles ${e.previewRoles.join(', ')}` : ''}`)
194
+ : ['None authored.']));
195
+ if (mistakes.length)
196
+ lines.push('', 'Explained mistakes (never use these as patterns):', '', ...mistakes.map(([id, e]) => `- ${code(id)} produces ${code(e.incorrect.diagnostic)}; correct: ${code(e.incorrect.correct)}`));
197
+ const customization = component.customization;
198
+ if (customization) {
199
+ lines.push('', '## Customization', '', `- Levels: ${customization.levels?.known === false ? 'unknown (not declared)' : `${customization.levels.value.join(', ')} (authored)`}`);
200
+ if (customization.variables?.length)
201
+ lines.push(`- Variables: ${customization.variables.map((v) => code(v.split('#')[1])).join(', ')}`);
202
+ }
203
+ if (component.contracts?.length)
204
+ lines.push('', '## Contracts', '', ...component.contracts.map((id) => `- ${code(id)}`));
205
+ const verified = (component.verification ?? []).filter((v) => v.provenance === 'verified');
206
+ if (verified.length) {
207
+ lines.push('', '## Verified', '');
208
+ for (const v of verified)
209
+ lines.push(`- ${v.facts.length} facts verified by ${code(v.suite)} in ${v.conditions.browsers.map((b) => `${b.name} ${b.version}`).join(', ')} (${v.source.test}, ${v.source.date.slice(0, 10)})`);
210
+ }
211
+ return `${lines
212
+ .join('\n')
213
+ .replace(/\n{3,}/g, '\n\n')
214
+ .trimEnd()}\n`;
215
+ }
216
+ function choosingReference(library, components, categories) {
217
+ const groups = new Map();
218
+ for (const c of components) {
219
+ const label = c.category ? (categories[c.category]?.label ?? c.category) : 'No category';
220
+ groups.set(label, [...(groups.get(label) ?? []), c]);
221
+ }
222
+ const lines = [
223
+ `# Choosing components in ${library.name}`,
224
+ '',
225
+ 'From authored `@useFor`, `@notFor`, and `@category`.',
226
+ '',
227
+ ];
228
+ let authored = 0;
229
+ for (const [label, list] of [...groups].sort(([a], [b]) => (a < b ? -1 : 1))) {
230
+ lines.push(`## ${label}`, '');
231
+ for (const c of list) {
232
+ const facts = [
233
+ ...(c.useFor ?? []).map((f) => ` - Use for: ${f.value}`),
234
+ ...(c.notFor ?? []).map((f) => ` - Not for: ${f.value}`),
235
+ ];
236
+ authored += facts.length;
237
+ lines.push(`- ${code(`<${c.tag}>`)}`, ...(facts.length ? facts : [' - Not authored.']));
238
+ }
239
+ lines.push('');
240
+ }
241
+ return { text: `${lines.join('\n').trimEnd()}\n`, notAuthored: authored === 0 };
242
+ }
243
+ const valueText = (value) => value && typeof value === 'object'
244
+ ? Object.entries(value)
245
+ .map(([k, v]) => `${k}: ${String(v)}`)
246
+ .join('; ')
247
+ : String(value ?? '');
248
+ function tokenReference(category, tokens, inspection) {
249
+ return `${[
250
+ `# Tokens: ${category}`,
251
+ '',
252
+ `Theme ${code(inspection['theme'])}, modes ${inspection['modes'].join(', ')}. From the theme's token inspection.`,
253
+ '',
254
+ table(['Token', 'CSS variable', 'Type', 'Value', 'Defined as', 'Origin'], tokens.map((t) => [
255
+ code(t.name),
256
+ code(t.cssVariable),
257
+ t.type,
258
+ valueText(t.value),
259
+ typeof t.definition === 'string' && /^\{[^}]+\}$/.test(t.definition)
260
+ ? `alias of ${code(t.definition.slice(1, -1))}`
261
+ : valueText(t.definition),
262
+ t.origin,
263
+ ])),
264
+ ]
265
+ .join('\n')
266
+ .trimEnd()}\n`;
267
+ }
268
+ function recipeReference(id, recipe) {
269
+ return `${[
270
+ `# Recipe ${recipe.name}`,
271
+ '',
272
+ `Id ${code(id)}.${recipe.container ? ` Container ${code(recipe.container.name ?? JSON.stringify(recipe.container))}.` : ''}`,
273
+ '',
274
+ table(['Region', 'Helper'], Object.entries(recipe.regions ?? {}).map(([region, r]) => [code(region), r.helper])),
275
+ ...(recipe.variants
276
+ ? [
277
+ '## Variants',
278
+ '',
279
+ ...Object.entries(recipe.variants).map(([k, v]) => `- ${code(k)}: ${v.join(', ')}`),
280
+ '',
281
+ ]
282
+ : []),
283
+ ...(recipe.conditions?.length
284
+ ? ['## Conditions', '', ...recipe.conditions.map((c) => `- ${code(c)}`), '']
285
+ : []),
286
+ ...(recipe.parts?.length
287
+ ? ['## Parts', '', ...recipe.parts.map((p) => `- ${code(p)}`), '']
288
+ : []),
289
+ `Layers: ${recipe.layers.join(' → ')}.`,
290
+ ]
291
+ .join('\n')
292
+ .trimEnd()}\n`;
293
+ }
294
+ function stylesheetReference(id, sheet) {
295
+ const list = (title, values) => values.length ? [`## ${title}`, '', values.map(code).join(', '), ''] : [];
296
+ return `${[
297
+ `# Stylesheet ${sheet.name}`,
298
+ '',
299
+ `Id ${code(id)}. Import ${code(sheet.import)} once per document; its rules are in the ${code(sheet.layer)} layer.`,
300
+ '',
301
+ ...(sheet.description ? [sheet.description, ''] : []),
302
+ ...list('Classes', sheet.classes.map((c) => `.${c}`)),
303
+ ...list('Variables an author may set', sheet.variables),
304
+ ...list('Tokens read', sheet.tokens),
305
+ ]
306
+ .join('\n')
307
+ .trimEnd()}\n`;
308
+ }
309
+ /** The public specifier of a package file, through exact or `*` pattern exports. */
310
+ function exportedAs(library, file) {
311
+ for (const [subpath, target] of Object.entries(library.manifest['exports'] ?? {})) {
312
+ const path = typeof target === 'string'
313
+ ? target
314
+ : (target?.development ?? target?.import ?? target?.default);
315
+ if (typeof path !== 'string')
316
+ continue;
317
+ if (!path.includes('*')) {
318
+ if (path.replace(/^\.\//, '') === file)
319
+ return `${library.name}${subpath.slice(1)}`;
320
+ continue;
321
+ }
322
+ const [before = '', after = ''] = path.replace(/^\.\//, '').split('*');
323
+ if (file.startsWith(before) &&
324
+ file.endsWith(after) &&
325
+ file.length >= before.length + after.length)
326
+ return `${library.name}${subpath.slice(1).replace('*', file.slice(before.length, file.length - after.length))}`;
327
+ }
328
+ return undefined;
329
+ }
330
+ function modelReference(library, id, model) {
331
+ const exported = exportedAs(library, model.schema);
332
+ return `${[
333
+ `# Model ${model.name} v${model.version}`,
334
+ '',
335
+ `Id ${code(id)}. JSON Schema: ${exported ? code(exported) : `${code(model.schema)} in the package (not exported)`}.`,
336
+ ...(model.kinds?.length ? ['', `Kinds: ${model.kinds.map(code).join(', ')}.`] : []),
337
+ ].join('\n')}\n`;
338
+ }
339
+ /** The guide: what to read first, small enough for every context window. */
340
+ function guide(project, library, components, categories, entries, packaged) {
341
+ const deps = Object.keys({
342
+ ...library.manifest['dependencies'],
343
+ ...library.manifest['peerDependencies'],
344
+ })
345
+ .filter((d) => d.startsWith('@oddkin/'))
346
+ .sort();
347
+ const metadata = library.metadata;
348
+ const propertyOnly = components.flatMap((c) => (c.propertyOnly ?? []).map((p) => code(`${c.tag}.${p.property.split('#')[1]}`)));
349
+ const userEvents = components.flatMap((c) => (c.events ?? [])
350
+ .filter((e) => e.intent.value === 'user')
351
+ .map((e) => code(e.event.split('#')[1])));
352
+ const contracts = Object.entries(metadata['contracts'] ?? {});
353
+ const commands = Object.entries(project.context.commands ?? {});
354
+ const refs = (kind) => entries.filter((e) => e.kind === kind);
355
+ // One stated rule for every `<prefix-name>` → `<package>/define/<name>` entry keeps the guide within budget.
356
+ const defineEntry = (c) => `${library.name}/define/${c.tag.slice(c.tag.indexOf('-') + 1)}`;
357
+ const regular = components.filter((c) => c.registration === defineEntry(c));
358
+ const irregular = components.filter((c) => c.registration !== defineEntry(c));
359
+ const byCategory = new Map();
360
+ for (const c of components) {
361
+ const label = c.category ? (categories[c.category]?.label ?? c.category) : 'Other';
362
+ byCategory.set(label, [...(byCategory.get(label) ?? []), c.tag]);
363
+ }
364
+ const groups = [...byCategory].sort(([a], [b]) => a === 'Other' ? 1 : b === 'Other' ? -1 : a.localeCompare(b));
365
+ const inspection = project.inspection;
366
+ const firstContract = contracts[0]?.[1];
367
+ // The theme's stylesheet: the CSS export named after the theme, in the package the inspection comes from.
368
+ const themeCss = inspection?.source
369
+ ? project.context.packages
370
+ .find((p) => p.name === inspection.source.package)
371
+ ?.exports?.find((specifier) => specifier.endsWith(`/css/${inspection.data['theme']}.css`))
372
+ : undefined;
373
+ return `${[
374
+ `# ${library.name} ${library.version}: agent guide`,
375
+ '',
376
+ packaged
377
+ ? `Generated from this package's published metadata when it was built (fingerprint in kit.json), so it matches the installed version. Read the reference for a component before using it.`
378
+ : `Generated by \`oddkin-kit\` (\`@oddkin/analyzer\`) from published metadata (fingerprint in kit.json). Read the reference for a component before using it. If \`${refreshCommand(library.name, '<out>')} --check\` reports the kit as stale, regenerate it first.`,
379
+ '',
380
+ '## Install and register',
381
+ '',
382
+ `- Package ${code(library.name)}${deps.length ? `, which depends on ${deps.map(code).join(', ')}` : ''}.`,
383
+ ...(regular.length
384
+ ? [
385
+ `- Register an element once before using its tag: ${code(`import '${library.name}/define/<name>';`)}, where \`<name>\` is the tag after its prefix (${code(`<${regular[0].tag}>`)}: ${code(`import '${regular[0].registration}';`)}). Class exports are in each component reference.`,
386
+ ]
387
+ : []),
388
+ ...(irregular.length
389
+ ? [
390
+ `- Registration entries${regular.length ? ' that differ from that rule' : ''}: ${irregular.map((c) => `${code(`<${c.tag}>`)} ${code(`import '${c.registration}';`)}`).join('; ')}.`,
391
+ ]
392
+ : []),
393
+ ...(themeCss
394
+ ? [
395
+ `- Link the theme's stylesheet once per document, ${code(themeCss)}: the design tokens every component reads.`,
396
+ ]
397
+ : []),
398
+ `- Without a bundler, an import map must map every bare specifier a page loads, the packages' own imports included${deps.length ? ` (${deps.map((d) => code(`${d}/…`)).join(', ')})` : ''}, to the file each package's \`exports\` names under the \`default\` condition. Never mix in a \`development\` build: it is a second copy of the package.`,
399
+ '',
400
+ '## Components',
401
+ '',
402
+ 'Each has a reference at `references/components/<tag>.md` with its exact API, guidance, and examples.',
403
+ '',
404
+ ...groups.map(([label, tags]) => `- **${label}:** ${tags.map((t) => code(`<${t}>`)).join(', ')}`),
405
+ '',
406
+ 'Choosing between them: references/choosing-components.md.',
407
+ '',
408
+ '## Rules from the metadata',
409
+ '',
410
+ propertyOnly.length
411
+ ? `- Property-only values (assign as JavaScript properties, never attributes): ${propertyOnly.join(', ')}.`
412
+ : '- No property-only values.',
413
+ userEvents.length
414
+ ? `- Events declared with user intent (authored; see each reference): ${[...new Set(userEvents)].join(', ')}. Other events state their intent, or that it is unknown, in each reference.`
415
+ : '- No events declare user intent.',
416
+ ...(firstContract
417
+ ? [
418
+ `- Contracts the components reference (${code(`${firstContract.package}/${firstContract.file}`)}): ${contracts.map(([id, c]) => `${code(id)} ${c.title}`).join('; ')}.`,
419
+ ]
420
+ : []),
421
+ '',
422
+ '## More references',
423
+ '',
424
+ ...(refs('tokens').length && inspection
425
+ ? [
426
+ `- Tokens (theme ${code(inspection.data['theme'])}): ${refs('tokens')
427
+ .map((e) => e.path)
428
+ .join(', ')}.`,
429
+ ]
430
+ : ['- Tokens: no token inspection was found for this library.']),
431
+ ...(refs('recipe').length
432
+ ? [
433
+ `- Recipes: ${refs('recipe')
434
+ .map((e) => e.path)
435
+ .join(', ')}.`,
436
+ ]
437
+ : []),
438
+ ...(refs('stylesheet').length
439
+ ? [
440
+ `- Document stylesheets (opt-in CSS: reset, layout classes, native-element recipes): ${refs('stylesheet')
441
+ .map((e) => e.path)
442
+ .join(', ')}.`,
443
+ ]
444
+ : []),
445
+ ...refs('composition').map((e) => `- Interfaces as data (validate and render JSON documents): ${e.path}.`),
446
+ ...(refs('model').length
447
+ ? [
448
+ `- Models: ${refs('model')
449
+ .map((e) => e.path)
450
+ .join(', ')}.`,
451
+ ]
452
+ : []),
453
+ packaged
454
+ ? `- JSON keyed by stable id, shipped beside this kit: ${code(`${library.name}/oddkin.metadata.json`)} (components, recipes, stylesheets, models), ${code(`${library.name}/custom-elements.json`)}${inspection?.source ? `, and the theme's tokens in ${code(`${inspection.source.package}${inspection.source.export.slice(1)}`)}` : ''}.`
455
+ : `- JSON contract bundles keyed by stable id: ${refs('json')
456
+ .map((e) => e.path)
457
+ .join(', ')}.`,
458
+ ...(commands.length && !packaged
459
+ ? ['', '## Workspace commands', '', ...commands.map(([k, v]) => `- ${k}: ${code(v)}`)]
460
+ : []),
461
+ ]
462
+ .join('\n')
463
+ .replace(/\n{3,}/g, '\n\n')
464
+ .trimEnd()}\n`;
465
+ }
466
+ /**
467
+ * Generate one library's kit: `files` maps kit-relative paths to text; `manifest` is kit.json
468
+ * (agent-kit.v1). `packaged`: the kit ships inside the package (no workspace inputs file, and a
469
+ * guide that needs no refresh command). `extra`: more references.
470
+ */
471
+ export function generateKit(project, name, { packaged = false, extra = [] } = {}) {
472
+ const library = project.libraries.find((l) => l.name === name);
473
+ if (!library)
474
+ throw new Error(`${name} publishes no oddkin.metadata.json in this workspace.`);
475
+ const inputs = packaged ? packagedInputs(project, library) : kitInputs(project, library);
476
+ const fingerprint = fingerprintOf(project, library, inputs);
477
+ const metadata = library.metadata;
478
+ const categories = metadata['categories'] ?? {};
479
+ const components = Object.values(metadata['components'] ?? {}).sort((a, b) => a.tag < b.tag ? -1 : 1);
480
+ const examples = Object.entries(metadata['examples'] ?? {});
481
+ const files = new Map();
482
+ const entries = [];
483
+ const add = (path, text, entry) => {
484
+ files.set(path, text);
485
+ if (entry)
486
+ entries.push({ ...entry, path });
487
+ };
488
+ for (const c of components)
489
+ add(`references/components/${c.tag}.md`, componentReference(library, c, categories, examples), {
490
+ id: `component:${c.tag}`,
491
+ kind: 'component',
492
+ provenance: 'extracted',
493
+ });
494
+ const choosing = choosingReference(library, components, categories);
495
+ add('references/choosing-components.md', choosing.text, {
496
+ id: 'choosing',
497
+ kind: 'choosing',
498
+ provenance: 'authored',
499
+ ...(choosing.notAuthored ? { notAuthored: true } : {}),
500
+ });
501
+ const bundle = (entriesById) => `${JSON.stringify({ fingerprint, entries: entriesById }, null, 2)}\n`;
502
+ const tokens = project.inspection?.data;
503
+ if (tokens) {
504
+ const byCategory = new Map();
505
+ for (const t of tokens['tokens'])
506
+ byCategory.set(t.category, [...(byCategory.get(t.category) ?? []), t]);
507
+ for (const [category, list] of [...byCategory].sort(([a], [b]) => (a < b ? -1 : 1)))
508
+ add(`references/tokens/${category}.md`, tokenReference(category, list, tokens), {
509
+ id: `tokens:${category}`,
510
+ kind: 'tokens',
511
+ provenance: 'extracted',
512
+ });
513
+ }
514
+ for (const [id, recipe] of Object.entries(metadata['recipes'] ?? {}))
515
+ add(`references/recipes/${recipe.name}.md`, recipeReference(id, recipe), {
516
+ id,
517
+ kind: 'recipe',
518
+ provenance: 'extracted',
519
+ });
520
+ for (const [id, sheet] of Object.entries(metadata['stylesheets'] ?? {}))
521
+ add(`references/stylesheets/${sheet.name}.md`, stylesheetReference(id, sheet), {
522
+ id,
523
+ kind: 'stylesheet',
524
+ provenance: 'extracted',
525
+ });
526
+ for (const [id, model] of Object.entries(metadata['models'] ?? {}))
527
+ add(`references/models/${model.name}.md`, modelReference(library, id, model), {
528
+ id,
529
+ kind: 'model',
530
+ provenance: 'extracted',
531
+ });
532
+ // A packaged kit points at the metadata, manifest, and token inspection the packages ship.
533
+ if (!packaged) {
534
+ add('json/components.json', bundle(Object.fromEntries(components.map((c) => [
535
+ `component:${c.tag}`,
536
+ {
537
+ package: library.name,
538
+ version: library.version,
539
+ declaration: declarationOf(library.cem, c) ?? null,
540
+ metadata: c,
541
+ },
542
+ ]))), { id: 'json:components', kind: 'json', provenance: 'extracted' });
543
+ if (tokens)
544
+ add('json/tokens.json', bundle(Object.fromEntries(tokens['tokens'].map((t) => [`token:${t.name}`, t]))), { id: 'json:tokens', kind: 'json', provenance: 'extracted' });
545
+ if (metadata['recipes'])
546
+ add('json/recipes.json', bundle(metadata['recipes']), {
547
+ id: 'json:recipes',
548
+ kind: 'json',
549
+ provenance: 'extracted',
550
+ });
551
+ if (metadata['stylesheets'])
552
+ add('json/stylesheets.json', bundle(metadata['stylesheets']), {
553
+ id: 'json:stylesheets',
554
+ kind: 'json',
555
+ provenance: 'extracted',
556
+ });
557
+ if (metadata['models'])
558
+ add('json/models.json', bundle(metadata['models']), {
559
+ id: 'json:models',
560
+ kind: 'json',
561
+ provenance: 'extracted',
562
+ });
563
+ }
564
+ for (const { path, text, entry } of extra)
565
+ add(path, text, entry);
566
+ const guideText = guide(project, library, components, categories, entries, packaged);
567
+ files.set('GUIDE.md', guideText);
568
+ const manifest = {
569
+ schema: KIT_SCHEMA,
570
+ library: { name: library.name, version: library.version },
571
+ fingerprint,
572
+ // The metadata the kit was generated from, so a packed kit can be checked against the packed file.
573
+ metadata: { file: 'oddkin.metadata.json', sha256: metadataHash(library.dir) },
574
+ guide: { path: 'GUIDE.md', budgetBytes: GUIDE_BUDGET, bytes: Buffer.byteLength(guideText) },
575
+ entries,
576
+ };
577
+ files.set('kit.json', `${JSON.stringify(manifest, null, 2)}\n`);
578
+ if (!packaged)
579
+ files.set(INPUTS_FILE, `${JSON.stringify({ inputs: fingerprint.inputs, files: Object.fromEntries([...inputs].sort(([a], [b]) => (a < b ? -1 : 1))) }, null, 2)}\n`);
580
+ return { files, manifest };
581
+ }
582
+ /** Write a kit into `dir`, replacing an earlier kit there; refuses a directory that holds something else. */
583
+ export function writeKit(dir, files) {
584
+ if (existsSync(dir) && readdirSync(dir).length) {
585
+ let previous;
586
+ try {
587
+ previous = JSON.parse(readFileSync(join(dir, 'kit.json'), 'utf8'));
588
+ }
589
+ catch {
590
+ previous = undefined;
591
+ }
592
+ if (previous?.schema !== KIT_SCHEMA)
593
+ throw new Error(`${dir} is not empty and holds no agent kit; refusing to replace it.`);
594
+ rmSync(dir, { recursive: true });
595
+ }
596
+ for (const [path, text] of files) {
597
+ mkdirSync(dirname(join(dir, path)), { recursive: true });
598
+ writeFileSync(join(dir, path), text);
599
+ }
600
+ }
601
+ /**
602
+ * Compare a written kit with the current inputs: `current`, or `stale` with the changed inputs
603
+ * (`agent.kit.stale`), or `missing`.
604
+ */
605
+ export function checkKit(dir, project, name) {
606
+ const library = project.libraries.find((l) => l.name === name);
607
+ if (!library || !existsSync(join(dir, 'kit.json')))
608
+ return { status: 'missing', changed: [] };
609
+ const kit = JSON.parse(readFileSync(join(dir, 'kit.json'), 'utf8'));
610
+ const recorded = existsSync(join(dir, INPUTS_FILE))
611
+ ? JSON.parse(readFileSync(join(dir, INPUTS_FILE), 'utf8')).files
612
+ : {};
613
+ const current = Object.fromEntries(kitInputs(project, library));
614
+ const changed = [...new Set([...Object.keys(recorded), ...Object.keys(current)])]
615
+ .filter((path) => recorded[path] !== current[path])
616
+ .sort()
617
+ .map((path) => `${path} (${!recorded[path] ? 'added' : !current[path] ? 'removed' : 'changed'})`);
618
+ const fingerprint = fingerprintOf(project, library, kitInputs(project, library));
619
+ if (kit.fingerprint.generator.version !== fingerprint.generator.version)
620
+ changed.push(`generator ${kit.fingerprint.generator.version} → ${fingerprint.generator.version}`);
621
+ for (const [pkg, version] of Object.entries(fingerprint.packages ?? {}))
622
+ if (kit.fingerprint.packages?.[pkg] !== version)
623
+ changed.push(`${pkg} ${kit.fingerprint.packages?.[pkg] ?? '(absent)'} → ${version}`);
624
+ return { status: changed.length ? 'stale' : 'current', changed };
625
+ }
626
+ //# sourceMappingURL=kit.js.map