@iodes/releasekit 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +90 -0
  5. package/dist/content.d.ts +38 -0
  6. package/dist/content.js +64 -0
  7. package/dist/export.d.ts +10 -0
  8. package/dist/export.js +57 -0
  9. package/dist/files.d.ts +15 -0
  10. package/dist/files.js +107 -0
  11. package/dist/git.d.ts +10 -0
  12. package/dist/git.js +68 -0
  13. package/dist/images.d.ts +32 -0
  14. package/dist/images.js +123 -0
  15. package/dist/install.d.ts +27 -0
  16. package/dist/install.js +63 -0
  17. package/dist/model.d.ts +318 -0
  18. package/dist/model.js +99 -0
  19. package/dist/project.d.ts +25 -0
  20. package/dist/project.js +103 -0
  21. package/dist/prompts.d.ts +8 -0
  22. package/dist/prompts.js +72 -0
  23. package/dist/schema-export.d.ts +1 -0
  24. package/dist/schema-export.js +10 -0
  25. package/dist/validate.d.ts +12 -0
  26. package/dist/validate.js +109 -0
  27. package/examples/README.md +14 -0
  28. package/examples/feature-briefs.yaml +89 -0
  29. package/examples/queue-action/README.md +15 -0
  30. package/examples/queue-action/alignment-edit.prompt.md +8 -0
  31. package/examples/queue-action/dark.png +0 -0
  32. package/examples/queue-action/dark.prompt.md +58 -0
  33. package/examples/queue-action/light.png +0 -0
  34. package/examples/queue-action/light.prompt.md +58 -0
  35. package/examples/queue-action/pair-review.md +33 -0
  36. package/examples/queue-action/scene.yaml +41 -0
  37. package/examples/release-notes.en-US.json +56 -0
  38. package/examples/release-notes.ko-KR.json +56 -0
  39. package/kit/references/composition-recipes.md +73 -0
  40. package/kit/references/format.md +24 -0
  41. package/kit/references/theme-pairing.md +53 -0
  42. package/kit/references/visual-language.md +69 -0
  43. package/kit/references/workflow.md +24 -0
  44. package/kit/references/writing.md +27 -0
  45. package/kit/skills/releasekit-draft/SKILL.md +10 -0
  46. package/kit/skills/releasekit-image/SKILL.md +16 -0
  47. package/kit/skills/releasekit-review/SKILL.md +12 -0
  48. package/kit/skills/releasekit-translate/SKILL.md +10 -0
  49. package/package.json +52 -0
  50. package/schemas/bundle.schema.json +178 -0
  51. package/schemas/config.schema.json +179 -0
  52. package/schemas/evidence.schema.json +96 -0
  53. package/schemas/note.schema.json +26 -0
  54. package/schemas/release.schema.json +275 -0
  55. package/schemas/visual.schema.json +174 -0
package/dist/images.js ADDED
@@ -0,0 +1,123 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import sharp from 'sharp';
4
+ import { themes, theme } from './model.js';
5
+ import { Project, editable } from './project.js';
6
+ import { readVisual, checkReferenceFiles } from './content.js';
7
+ import { digest, identifier, exists, write, writeYaml } from './files.js';
8
+ import { imagePrompt, sceneHash } from './prompts.js';
9
+ export async function inspectImage(bytes) {
10
+ const image = sharp(bytes, { limitInputPixels: 16_777_216, failOn: 'warning' });
11
+ const metadata = await image.metadata();
12
+ if (!metadata.format || !['png', 'jpeg', 'webp'].includes(metadata.format) || !metadata.width || !metadata.height || (metadata.pages ?? 1) > 1) {
13
+ throw new Error('Use a still PNG, JPEG, or WebP image.');
14
+ }
15
+ await image.stats(); // Decode the file so a plausible but truncated header cannot pass import.
16
+ const rotated = (metadata.orientation ?? 1) >= 5;
17
+ return {
18
+ width: rotated ? metadata.height : metadata.width,
19
+ height: rotated ? metadata.width : metadata.height,
20
+ extension: metadata.format === 'jpeg' ? 'jpg' : metadata.format,
21
+ sha256: digest(bytes),
22
+ };
23
+ }
24
+ export async function planImages(project, version) {
25
+ const release = await project.release(version);
26
+ const requests = [];
27
+ let ready = 0;
28
+ for (const note of release.notes.filter(n => n.image)) {
29
+ const visual = await readVisual(project, version, note.id);
30
+ const pair = visual.variants;
31
+ const invalidPair = release.visuals.themes === 'both' && pair.dark && pair.light &&
32
+ (pair.dark.sha256 === pair.light.sha256 || pair.dark.width !== pair.light.width || pair.dark.height !== pair.light.height);
33
+ for (const variant of themes(release.visuals)) {
34
+ const expected = sceneHash(visual.scene, release.visuals, variant);
35
+ const asset = visual.variants[variant];
36
+ const file = asset && await project.releaseFile(version, asset.file);
37
+ const present = !!file && await exists(file);
38
+ const current = !!asset && present && asset.sceneHash === expected && digest(await fs.readFile(file)) === asset.sha256 && !(invalidPair && variant === 'light');
39
+ if (current) {
40
+ ready++;
41
+ continue;
42
+ }
43
+ editable(release);
44
+ await checkReferenceFiles(project, visual.scene.references);
45
+ const promptFile = await project.releaseFile(version, `prompts/${note.id}.${variant}.md`);
46
+ await write(promptFile, imagePrompt(visual.scene, release.visuals, variant));
47
+ const otherTheme = variant === 'dark' ? 'light' : 'dark';
48
+ const other = visual.variants[otherTheme];
49
+ let compositionReference = null;
50
+ if (other && other.sceneHash === sceneHash(visual.scene, release.visuals, otherTheme)) {
51
+ const reference = await project.releaseFile(version, other.file);
52
+ if (await exists(reference) && digest(await fs.readFile(reference)) === other.sha256)
53
+ compositionReference = reference;
54
+ }
55
+ requests.push({ note: note.id, theme: variant, reason: present ? 'stale' : 'missing', promptFile, compositionReference });
56
+ }
57
+ }
58
+ return {
59
+ version, configuredThemes: themes(release.visuals), requestedAssets: ready + requests.length,
60
+ readyAssets: ready, pendingAssets: requests.length, requests,
61
+ costNote: 'Counts describe required output assets, not provider prices or a guarantee of one tool call per asset. No image service was called.',
62
+ };
63
+ }
64
+ export async function importImage(project, version, noteId, variant, source) {
65
+ const release = await project.release(version);
66
+ editable(release);
67
+ identifier(noteId);
68
+ theme.parse(variant);
69
+ if (!release.notes.some(n => n.id === noteId && n.image))
70
+ throw new Error(`No image-enabled note named ${noteId}.`);
71
+ if (!themes(release.visuals).includes(variant))
72
+ throw new Error(`Theme ${variant} is not enabled for this release. Update the project setting and sync the draft first.`);
73
+ const visual = await readVisual(project, version, noteId);
74
+ const bytes = await fs.readFile(path.resolve(project.root, source));
75
+ const inspected = await inspectImage(bytes);
76
+ const file = `assets/${noteId}.${variant}.${inspected.sha256.slice(0, 12)}.${inspected.extension}`;
77
+ const destination = await project.releaseFile(version, file);
78
+ if (await exists(destination)) {
79
+ if (digest(await fs.readFile(destination)) !== inspected.sha256)
80
+ throw new Error('The asset destination has conflicting content.');
81
+ }
82
+ else
83
+ await write(destination, bytes);
84
+ visual.variants[variant] = {
85
+ file, sha256: inspected.sha256, sceneHash: sceneHash(visual.scene, release.visuals, variant),
86
+ width: inspected.width, height: inspected.height,
87
+ };
88
+ await writeYaml(await project.releaseFile(version, `visuals/${noteId}.yaml`), visual);
89
+ return visual.variants[variant];
90
+ }
91
+ export async function validateImages(project, version, noteId, visual, errors, warnings) {
92
+ const release = await project.release(version);
93
+ for (const variant of themes(release.visuals)) {
94
+ const expected = sceneHash(visual.scene, release.visuals, variant);
95
+ const asset = visual.variants[variant];
96
+ if (!asset) {
97
+ errors.push(`${noteId}: ${variant} image is pending.`);
98
+ continue;
99
+ }
100
+ try {
101
+ if (asset.sceneHash !== expected)
102
+ errors.push(`${noteId}: ${variant} image was created for an older scene or palette.`);
103
+ const actual = await inspectImage(await fs.readFile(await project.releaseFile(version, asset.file)));
104
+ if (actual.sha256 !== asset.sha256 || actual.width !== asset.width || actual.height !== asset.height) {
105
+ errors.push(`${noteId}: ${variant} asset changed after import; import the selected file again.`);
106
+ }
107
+ const requestedRatio = release.visuals.width / release.visuals.height;
108
+ if (Math.abs(actual.width / actual.height / requestedRatio - 1) > 0.05) {
109
+ warnings.push(`${noteId}: ${variant} aspect ratio differs from the project target; review its framing.`);
110
+ }
111
+ }
112
+ catch (error) {
113
+ errors.push(`${noteId}/${variant}: ${error instanceof Error ? error.message : error}`);
114
+ }
115
+ }
116
+ if (release.visuals.themes === 'both' && visual.variants.dark && visual.variants.light) {
117
+ const { dark, light } = visual.variants;
118
+ if (dark.sha256 === light.sha256)
119
+ errors.push(`${noteId}: the two theme variants are identical; provide a real pair or select a single theme.`);
120
+ if (dark.width !== light.width || dark.height !== light.height)
121
+ errors.push(`${noteId}: paired variants must have identical dimensions.`);
122
+ }
123
+ }
@@ -0,0 +1,27 @@
1
+ import { Project } from './project.js';
2
+ import { type ProjectConfig } from './model.js';
3
+ export declare function installSkills(project: Project): Promise<{
4
+ tools: ("claude" | "codex" | "cursor")[];
5
+ written: string[];
6
+ conflicts: string[];
7
+ hints: {
8
+ codex: string;
9
+ claude: string;
10
+ cursor: string;
11
+ };
12
+ }>;
13
+ export declare function initProject(project: Project, options: {
14
+ product?: string;
15
+ tools?: ProjectConfig['tools'];
16
+ themes?: ProjectConfig['visuals']['themes'];
17
+ }): Promise<{
18
+ tools: ("claude" | "codex" | "cursor")[];
19
+ written: string[];
20
+ conflicts: string[];
21
+ hints: {
22
+ codex: string;
23
+ claude: string;
24
+ cursor: string;
25
+ };
26
+ config: string;
27
+ }>;
@@ -0,0 +1,63 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { z } from 'zod';
5
+ import { Project } from './project.js';
6
+ import { configSchema, defaultConfig } from './model.js';
7
+ import { exists, within, write, writeYaml, digest } from './files.js';
8
+ const resources = fileURLToPath(new URL('../kit/', import.meta.url));
9
+ const names = ['releasekit-draft', 'releasekit-image', 'releasekit-translate', 'releasekit-review'];
10
+ const managedSchema = z.record(z.string(), z.string().regex(/^[a-f0-9]{64}$/));
11
+ export async function installSkills(project) {
12
+ const config = await project.config();
13
+ const marker = await project.content('managed-skills.json');
14
+ const managed = await exists(marker) ? managedSchema.parse(JSON.parse(await fs.readFile(marker, 'utf8'))) : {};
15
+ const roots = new Set(config.tools.map(tool => tool === 'claude' ? '.claude/skills' : '.agents/skills'));
16
+ const references = (await fs.readdir(path.join(resources, 'references'))).filter(file => file.endsWith('.md')).sort();
17
+ const written = [], conflicts = [];
18
+ for (const root of roots) {
19
+ for (const name of names) {
20
+ const sourceFiles = [
21
+ { source: path.join(resources, 'skills', name, 'SKILL.md'), destination: `${root}/${name}/SKILL.md` },
22
+ ...references.map(file => ({ source: path.join(resources, 'references', file), destination: `${root}/${name}/references/${file}` })),
23
+ ];
24
+ for (const item of sourceFiles) {
25
+ const content = await fs.readFile(item.source);
26
+ const expected = digest(content);
27
+ const destination = await within(project.root, item.destination);
28
+ if (await exists(destination)) {
29
+ const actual = digest(await fs.readFile(destination));
30
+ if (actual === expected) {
31
+ managed[item.destination] = expected;
32
+ continue;
33
+ }
34
+ if (actual !== managed[item.destination]) {
35
+ conflicts.push(item.destination);
36
+ continue;
37
+ }
38
+ }
39
+ await write(destination, content);
40
+ managed[item.destination] = expected;
41
+ written.push(item.destination);
42
+ }
43
+ }
44
+ }
45
+ await write(marker, JSON.stringify(managed, null, 2) + '\n');
46
+ return { tools: config.tools, written, conflicts, hints: {
47
+ codex: 'Use $releasekit-draft, $releasekit-image, $releasekit-translate, or $releasekit-review.',
48
+ claude: 'Use /releasekit-draft, /releasekit-image, /releasekit-translate, or /releasekit-review.',
49
+ cursor: 'Use the installed releasekit-* skills from the agent skill picker or name them in your request.',
50
+ } };
51
+ }
52
+ export async function initProject(project, options) {
53
+ const file = await project.content('config.yaml');
54
+ if (await exists(file))
55
+ throw new Error('ReleaseKit is already initialized. Edit config.yaml for project settings or run update for skills.');
56
+ const config = defaultConfig(options.product ?? path.basename(project.root));
57
+ if (options.tools)
58
+ config.tools = [...new Set(options.tools)];
59
+ if (options.themes)
60
+ config.visuals.themes = options.themes;
61
+ await writeYaml(file, configSchema.parse(config));
62
+ return { config: file, ...await installSkills(project) };
63
+ }
@@ -0,0 +1,318 @@
1
+ import { z } from 'zod';
2
+ export declare const segment: z.ZodString;
3
+ export declare const sha: z.ZodString;
4
+ export declare const locale: z.ZodString;
5
+ export declare const theme: z.ZodEnum<{
6
+ dark: "dark";
7
+ light: "light";
8
+ }>;
9
+ export type Theme = z.infer<typeof theme>;
10
+ export declare const paletteSchema: z.ZodObject<{
11
+ canvas: z.ZodString;
12
+ surface: z.ZodString;
13
+ raised: z.ZodString;
14
+ primary: z.ZodString;
15
+ secondary: z.ZodString;
16
+ divider: z.ZodString;
17
+ }, z.core.$strict>;
18
+ export declare const visualPolicySchema: z.ZodObject<{
19
+ themes: z.ZodEnum<{
20
+ both: "both";
21
+ dark: "dark";
22
+ light: "light";
23
+ }>;
24
+ preset: z.ZodLiteral<"quiet-product">;
25
+ width: z.ZodNumber;
26
+ height: z.ZodNumber;
27
+ accent: z.ZodString;
28
+ dark: z.ZodObject<{
29
+ canvas: z.ZodString;
30
+ surface: z.ZodString;
31
+ raised: z.ZodString;
32
+ primary: z.ZodString;
33
+ secondary: z.ZodString;
34
+ divider: z.ZodString;
35
+ }, z.core.$strict>;
36
+ light: z.ZodObject<{
37
+ canvas: z.ZodString;
38
+ surface: z.ZodString;
39
+ raised: z.ZodString;
40
+ primary: z.ZodString;
41
+ secondary: z.ZodString;
42
+ divider: z.ZodString;
43
+ }, z.core.$strict>;
44
+ }, z.core.$strict>;
45
+ export type VisualPolicy = z.infer<typeof visualPolicySchema>;
46
+ export declare const configSchema: z.ZodObject<{
47
+ schemaVersion: z.ZodLiteral<1>;
48
+ product: z.ZodString;
49
+ sourceLocale: z.ZodString;
50
+ locales: z.ZodArray<z.ZodString>;
51
+ history: z.ZodObject<{
52
+ limit: z.ZodNumber;
53
+ }, z.core.$strict>;
54
+ visuals: z.ZodObject<{
55
+ themes: z.ZodEnum<{
56
+ both: "both";
57
+ dark: "dark";
58
+ light: "light";
59
+ }>;
60
+ preset: z.ZodLiteral<"quiet-product">;
61
+ width: z.ZodNumber;
62
+ height: z.ZodNumber;
63
+ accent: z.ZodString;
64
+ dark: z.ZodObject<{
65
+ canvas: z.ZodString;
66
+ surface: z.ZodString;
67
+ raised: z.ZodString;
68
+ primary: z.ZodString;
69
+ secondary: z.ZodString;
70
+ divider: z.ZodString;
71
+ }, z.core.$strict>;
72
+ light: z.ZodObject<{
73
+ canvas: z.ZodString;
74
+ surface: z.ZodString;
75
+ raised: z.ZodString;
76
+ primary: z.ZodString;
77
+ secondary: z.ZodString;
78
+ divider: z.ZodString;
79
+ }, z.core.$strict>;
80
+ }, z.core.$strict>;
81
+ tools: z.ZodArray<z.ZodEnum<{
82
+ claude: "claude";
83
+ codex: "codex";
84
+ cursor: "cursor";
85
+ }>>;
86
+ }, z.core.$strict>;
87
+ export type ProjectConfig = z.infer<typeof configSchema>;
88
+ export declare const sourceSchema: z.ZodObject<{
89
+ fromRef: z.ZodNullable<z.ZodString>;
90
+ fromSha: z.ZodNullable<z.ZodString>;
91
+ toRef: z.ZodString;
92
+ toSha: z.ZodString;
93
+ }, z.core.$strict>;
94
+ export declare const noteMetaSchema: z.ZodObject<{
95
+ id: z.ZodString;
96
+ category: z.ZodEnum<{
97
+ feature: "feature";
98
+ fix: "fix";
99
+ improvement: "improvement";
100
+ security: "security";
101
+ }>;
102
+ commits: z.ZodArray<z.ZodString>;
103
+ paths: z.ZodArray<z.ZodString>;
104
+ image: z.ZodBoolean;
105
+ }, z.core.$strict>;
106
+ export type NoteMeta = z.infer<typeof noteMetaSchema>;
107
+ export declare const releaseSchema: z.ZodObject<{
108
+ schemaVersion: z.ZodLiteral<1>;
109
+ version: z.ZodString;
110
+ releasedAt: z.ZodISODate;
111
+ previous: z.ZodNullable<z.ZodString>;
112
+ status: z.ZodEnum<{
113
+ draft: "draft";
114
+ ready: "ready";
115
+ }>;
116
+ source: z.ZodObject<{
117
+ fromRef: z.ZodNullable<z.ZodString>;
118
+ fromSha: z.ZodNullable<z.ZodString>;
119
+ toRef: z.ZodString;
120
+ toSha: z.ZodString;
121
+ }, z.core.$strict>;
122
+ sourceLocale: z.ZodString;
123
+ locales: z.ZodArray<z.ZodString>;
124
+ visuals: z.ZodObject<{
125
+ themes: z.ZodEnum<{
126
+ both: "both";
127
+ dark: "dark";
128
+ light: "light";
129
+ }>;
130
+ preset: z.ZodLiteral<"quiet-product">;
131
+ width: z.ZodNumber;
132
+ height: z.ZodNumber;
133
+ accent: z.ZodString;
134
+ dark: z.ZodObject<{
135
+ canvas: z.ZodString;
136
+ surface: z.ZodString;
137
+ raised: z.ZodString;
138
+ primary: z.ZodString;
139
+ secondary: z.ZodString;
140
+ divider: z.ZodString;
141
+ }, z.core.$strict>;
142
+ light: z.ZodObject<{
143
+ canvas: z.ZodString;
144
+ surface: z.ZodString;
145
+ raised: z.ZodString;
146
+ primary: z.ZodString;
147
+ secondary: z.ZodString;
148
+ divider: z.ZodString;
149
+ }, z.core.$strict>;
150
+ }, z.core.$strict>;
151
+ notes: z.ZodArray<z.ZodObject<{
152
+ id: z.ZodString;
153
+ category: z.ZodEnum<{
154
+ feature: "feature";
155
+ fix: "fix";
156
+ improvement: "improvement";
157
+ security: "security";
158
+ }>;
159
+ commits: z.ZodArray<z.ZodString>;
160
+ paths: z.ZodArray<z.ZodString>;
161
+ image: z.ZodBoolean;
162
+ }, z.core.$strict>>;
163
+ emptyReason: z.ZodNullable<z.ZodString>;
164
+ contentHash: z.ZodNullable<z.ZodString>;
165
+ }, z.core.$strict>;
166
+ export type Release = z.infer<typeof releaseSchema>;
167
+ export declare const noteTextSchema: z.ZodObject<{
168
+ title: z.ZodString;
169
+ alt: z.ZodString;
170
+ sourceHash: z.ZodNullable<z.ZodString>;
171
+ }, z.core.$strict>;
172
+ export type NoteText = z.infer<typeof noteTextSchema> & {
173
+ body: string;
174
+ };
175
+ export declare const archetypeSchema: z.ZodEnum<{
176
+ "data-view": "data-view";
177
+ "device-view": "device-view";
178
+ "editorial-scene": "editorial-scene";
179
+ "icon-tile": "icon-tile";
180
+ "object-detail": "object-detail";
181
+ "spatial-view": "spatial-view";
182
+ "symbol-pair": "symbol-pair";
183
+ "ui-detail": "ui-detail";
184
+ }>;
185
+ export declare const sceneSchema: z.ZodObject<{
186
+ archetype: z.ZodEnum<{
187
+ "data-view": "data-view";
188
+ "device-view": "device-view";
189
+ "editorial-scene": "editorial-scene";
190
+ "icon-tile": "icon-tile";
191
+ "object-detail": "object-detail";
192
+ "spatial-view": "spatial-view";
193
+ "symbol-pair": "symbol-pair";
194
+ "ui-detail": "ui-detail";
195
+ }>;
196
+ subject: z.ZodString;
197
+ message: z.ZodString;
198
+ focus: z.ZodString;
199
+ composition: z.ZodString;
200
+ context: z.ZodString;
201
+ elements: z.ZodArray<z.ZodString>;
202
+ preserve: z.ZodArray<z.ZodString>;
203
+ avoid: z.ZodArray<z.ZodString>;
204
+ text: z.ZodArray<z.ZodString>;
205
+ references: z.ZodArray<z.ZodString>;
206
+ }, z.core.$strict>;
207
+ export type Scene = z.infer<typeof sceneSchema>;
208
+ export declare const assetSchema: z.ZodObject<{
209
+ file: z.ZodString;
210
+ sha256: z.ZodString;
211
+ sceneHash: z.ZodString;
212
+ width: z.ZodNumber;
213
+ height: z.ZodNumber;
214
+ }, z.core.$strict>;
215
+ export declare const visualSchema: z.ZodObject<{
216
+ schemaVersion: z.ZodLiteral<1>;
217
+ scene: z.ZodObject<{
218
+ archetype: z.ZodEnum<{
219
+ "data-view": "data-view";
220
+ "device-view": "device-view";
221
+ "editorial-scene": "editorial-scene";
222
+ "icon-tile": "icon-tile";
223
+ "object-detail": "object-detail";
224
+ "spatial-view": "spatial-view";
225
+ "symbol-pair": "symbol-pair";
226
+ "ui-detail": "ui-detail";
227
+ }>;
228
+ subject: z.ZodString;
229
+ message: z.ZodString;
230
+ focus: z.ZodString;
231
+ composition: z.ZodString;
232
+ context: z.ZodString;
233
+ elements: z.ZodArray<z.ZodString>;
234
+ preserve: z.ZodArray<z.ZodString>;
235
+ avoid: z.ZodArray<z.ZodString>;
236
+ text: z.ZodArray<z.ZodString>;
237
+ references: z.ZodArray<z.ZodString>;
238
+ }, z.core.$strict>;
239
+ variants: z.ZodObject<{
240
+ dark: z.ZodOptional<z.ZodObject<{
241
+ file: z.ZodString;
242
+ sha256: z.ZodString;
243
+ sceneHash: z.ZodString;
244
+ width: z.ZodNumber;
245
+ height: z.ZodNumber;
246
+ }, z.core.$strict>>;
247
+ light: z.ZodOptional<z.ZodObject<{
248
+ file: z.ZodString;
249
+ sha256: z.ZodString;
250
+ sceneHash: z.ZodString;
251
+ width: z.ZodNumber;
252
+ height: z.ZodNumber;
253
+ }, z.core.$strict>>;
254
+ }, z.core.$strict>;
255
+ }, z.core.$strict>;
256
+ export type Visual = z.infer<typeof visualSchema>;
257
+ export declare const evidenceSchema: z.ZodObject<{
258
+ schemaVersion: z.ZodLiteral<1>;
259
+ source: z.ZodObject<{
260
+ fromRef: z.ZodNullable<z.ZodString>;
261
+ fromSha: z.ZodNullable<z.ZodString>;
262
+ toRef: z.ZodString;
263
+ toSha: z.ZodString;
264
+ }, z.core.$strict>;
265
+ commits: z.ZodArray<z.ZodObject<{
266
+ sha: z.ZodString;
267
+ subject: z.ZodString;
268
+ }, z.core.$strict>>;
269
+ files: z.ZodArray<z.ZodObject<{
270
+ status: z.ZodString;
271
+ path: z.ZodString;
272
+ oldPath: z.ZodOptional<z.ZodString>;
273
+ }, z.core.$strict>>;
274
+ }, z.core.$strict>;
275
+ export type Evidence = z.infer<typeof evidenceSchema>;
276
+ export declare const bundleSchema: z.ZodObject<{
277
+ schemaVersion: z.ZodLiteral<1>;
278
+ currentVersion: z.ZodString;
279
+ locale: z.ZodString;
280
+ releases: z.ZodArray<z.ZodObject<{
281
+ version: z.ZodString;
282
+ releasedAt: z.ZodISODate;
283
+ previous: z.ZodNullable<z.ZodString>;
284
+ notes: z.ZodArray<z.ZodObject<{
285
+ id: z.ZodString;
286
+ category: z.ZodEnum<{
287
+ feature: "feature";
288
+ fix: "fix";
289
+ improvement: "improvement";
290
+ security: "security";
291
+ }>;
292
+ title: z.ZodString;
293
+ bodyMarkdown: z.ZodString;
294
+ image: z.ZodNullable<z.ZodObject<{
295
+ alt: z.ZodString;
296
+ fallbackTheme: z.ZodEnum<{
297
+ dark: "dark";
298
+ light: "light";
299
+ }>;
300
+ variants: z.ZodObject<{
301
+ dark: z.ZodOptional<z.ZodObject<{
302
+ src: z.ZodString;
303
+ width: z.ZodNumber;
304
+ height: z.ZodNumber;
305
+ }, z.core.$strict>>;
306
+ light: z.ZodOptional<z.ZodObject<{
307
+ src: z.ZodString;
308
+ width: z.ZodNumber;
309
+ height: z.ZodNumber;
310
+ }, z.core.$strict>>;
311
+ }, z.core.$strict>;
312
+ }, z.core.$strict>>;
313
+ }, z.core.$strict>>;
314
+ }, z.core.$strict>>;
315
+ }, z.core.$strict>;
316
+ export type Bundle = z.infer<typeof bundleSchema>;
317
+ export declare function themes(policy: VisualPolicy): Theme[];
318
+ export declare function defaultConfig(product: string): ProjectConfig;
package/dist/model.js ADDED
@@ -0,0 +1,99 @@
1
+ import { z } from 'zod';
2
+ export const segment = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,95}$/);
3
+ export const sha = z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
4
+ export const locale = z.string().regex(/^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/);
5
+ export const theme = z.enum(['dark', 'light']);
6
+ const color = z.string().regex(/^#[a-fA-F0-9]{6}$/);
7
+ export const paletteSchema = z.strictObject({
8
+ canvas: color, surface: color, raised: color, primary: color, secondary: color, divider: color,
9
+ });
10
+ export const visualPolicySchema = z.strictObject({
11
+ themes: z.enum(['both', 'dark', 'light']),
12
+ preset: z.literal('quiet-product'),
13
+ width: z.number().int().min(256).max(4096),
14
+ height: z.number().int().min(256).max(4096),
15
+ accent: color,
16
+ dark: paletteSchema,
17
+ light: paletteSchema,
18
+ });
19
+ export const configSchema = z.strictObject({
20
+ schemaVersion: z.literal(1), product: z.string().min(1),
21
+ sourceLocale: locale, locales: z.array(locale).min(1),
22
+ history: z.strictObject({ limit: z.number().int().min(1).max(100) }),
23
+ visuals: visualPolicySchema,
24
+ tools: z.array(z.enum(['codex', 'claude', 'cursor'])),
25
+ });
26
+ export const sourceSchema = z.strictObject({
27
+ fromRef: z.string().nullable(), fromSha: sha.nullable(), toRef: z.string(), toSha: sha,
28
+ });
29
+ export const noteMetaSchema = z.strictObject({
30
+ id: segment,
31
+ category: z.enum(['feature', 'improvement', 'fix', 'security']),
32
+ commits: z.array(sha), paths: z.array(z.string()), image: z.boolean(),
33
+ });
34
+ export const releaseSchema = z.strictObject({
35
+ schemaVersion: z.literal(1), version: segment, releasedAt: z.iso.date(),
36
+ previous: segment.nullable(), status: z.enum(['draft', 'ready']),
37
+ source: sourceSchema, sourceLocale: locale, locales: z.array(locale).min(1),
38
+ visuals: visualPolicySchema, notes: z.array(noteMetaSchema),
39
+ emptyReason: z.string().nullable(), contentHash: z.string().nullable(),
40
+ });
41
+ export const noteTextSchema = z.strictObject({
42
+ title: z.string().min(1), alt: z.string(), sourceHash: z.string().nullable(),
43
+ });
44
+ export const archetypeSchema = z.enum([
45
+ 'icon-tile', 'symbol-pair', 'ui-detail', 'device-view', 'object-detail',
46
+ 'spatial-view', 'data-view', 'editorial-scene',
47
+ ]);
48
+ export const sceneSchema = z.strictObject({
49
+ archetype: archetypeSchema,
50
+ subject: z.string().min(1), message: z.string().min(1),
51
+ focus: z.string().min(1), composition: z.string().min(1),
52
+ context: z.string(), elements: z.array(z.string()),
53
+ preserve: z.array(z.string()), avoid: z.array(z.string()),
54
+ text: z.array(z.string()), references: z.array(z.string()),
55
+ });
56
+ export const assetSchema = z.strictObject({
57
+ file: z.string().min(1), sha256: z.string().regex(/^[a-f0-9]{64}$/),
58
+ sceneHash: z.string().regex(/^[a-f0-9]{64}$/),
59
+ width: z.number().int().positive(), height: z.number().int().positive(),
60
+ });
61
+ export const visualSchema = z.strictObject({
62
+ schemaVersion: z.literal(1), scene: sceneSchema,
63
+ variants: z.strictObject({ dark: assetSchema.optional(), light: assetSchema.optional() }),
64
+ });
65
+ export const evidenceSchema = z.strictObject({
66
+ schemaVersion: z.literal(1), source: sourceSchema,
67
+ commits: z.array(z.strictObject({ sha, subject: z.string() })),
68
+ files: z.array(z.strictObject({ status: z.string(), path: z.string(), oldPath: z.string().optional() })),
69
+ });
70
+ const exportedImage = z.strictObject({
71
+ src: z.string(), width: z.number().int().positive(), height: z.number().int().positive(),
72
+ });
73
+ export const bundleSchema = z.strictObject({
74
+ schemaVersion: z.literal(1), currentVersion: segment, locale,
75
+ releases: z.array(z.strictObject({
76
+ version: segment, releasedAt: z.iso.date(), previous: segment.nullable(),
77
+ notes: z.array(z.strictObject({
78
+ id: segment, category: noteMetaSchema.shape.category, title: z.string(), bodyMarkdown: z.string(),
79
+ image: z.strictObject({
80
+ alt: z.string(), fallbackTheme: theme,
81
+ variants: z.strictObject({ dark: exportedImage.optional(), light: exportedImage.optional() }),
82
+ }).nullable(),
83
+ })),
84
+ })),
85
+ });
86
+ export function themes(policy) {
87
+ return policy.themes === 'both' ? ['dark', 'light'] : [policy.themes];
88
+ }
89
+ export function defaultConfig(product) {
90
+ return {
91
+ schemaVersion: 1, product, sourceLocale: 'ko-KR', locales: ['ko-KR', 'en-US'],
92
+ history: { limit: 3 }, tools: ['codex', 'claude', 'cursor'],
93
+ visuals: {
94
+ themes: 'both', preset: 'quiet-product', width: 1280, height: 800, accent: '#4678ED',
95
+ dark: { canvas: '#242527', surface: '#18191B', raised: '#343638', primary: '#B9BBBE', secondary: '#777B80', divider: '#46494D' },
96
+ light: { canvas: '#F7F8FA', surface: '#FFFFFF', raised: '#ECEEF1', primary: '#494D52', secondary: '#969BA2', divider: '#DDE0E5' },
97
+ },
98
+ };
99
+ }
@@ -0,0 +1,25 @@
1
+ import { type ProjectConfig, type Release, type Evidence } from './model.js';
2
+ export declare class Project {
3
+ readonly root: string;
4
+ constructor(root: string);
5
+ static find(cwd: string): Project;
6
+ content(relative: string): Promise<string>;
7
+ config(): Promise<ProjectConfig>;
8
+ releaseDir(version: string): Promise<string>;
9
+ releaseFile(version: string, relative: string): Promise<string>;
10
+ release(version: string): Promise<Release>;
11
+ save(release: Release): Promise<void>;
12
+ versions(): Promise<string[]>;
13
+ evidence(version: string): Promise<Evidence>;
14
+ history(version: string, limit: number): Promise<Release[]>;
15
+ }
16
+ export declare function editable(release: Release): void;
17
+ export interface PrepareOptions {
18
+ from?: string;
19
+ to?: string;
20
+ previous?: string;
21
+ fromRoot?: boolean;
22
+ firstRelease?: boolean;
23
+ date?: string;
24
+ }
25
+ export declare function prepare(project: Project, version: string, options: PrepareOptions): Promise<Release>;