@iodes/releasekit 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -26
- package/dist/cli.js +7 -2
- package/dist/git.d.ts +18 -6
- package/dist/git.js +16 -7
- package/dist/install.js +3 -3
- package/dist/model.d.ts +37 -19
- package/dist/model.js +9 -8
- package/dist/project.d.ts +7 -2
- package/dist/project.js +62 -16
- package/dist/schema-export.js +9 -2
- package/dist/validate.js +16 -12
- package/kit/references/adoption.md +57 -0
- package/kit/references/format.md +10 -3
- package/kit/references/workflow.md +114 -9
- package/kit/references/writing.md +4 -0
- package/kit/skills/releasekit-draft/SKILL.md +9 -3
- package/kit/skills/releasekit-finalize/SKILL.md +20 -0
- package/kit/skills/releasekit-image/SKILL.md +6 -0
- package/package.json +1 -1
- package/schemas/config.schema.json +87 -0
- package/schemas/release.schema.json +7 -0
- package/kit/skills/releasekit-review/SKILL.md +0 -12
- package/kit/skills/releasekit-translate/SKILL.md +0 -10
- package/schemas/evidence.schema.json +0 -96
package/README.md
CHANGED
|
@@ -53,29 +53,37 @@ Run inside your product's Git repository:
|
|
|
53
53
|
releasekit init --tools codex,claude,cursor --themes both
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
Choose the tools you use: `codex`, `claude`, `cursor`, or a comma-separated list.
|
|
56
|
+
Choose the tools you use: `codex`, `claude`, `cursor`, or a comma-separated list. New projects use English (`en-US`) as the original language, with no translations enabled until selected. Edit `releasekit/config.yaml` to set your product name, intentional language overrides, and visual settings. Setup prints the skill invocation hints for each tool.
|
|
57
57
|
|
|
58
58
|
### 3. Ask your agent
|
|
59
59
|
|
|
60
|
-
Start with the installed `releasekit-draft` skill. Example conversation with an image tool available:
|
|
60
|
+
Start with the installed `releasekit-draft` skill. Example conversation with a Korean-speaking user, shown here in English, and an image tool available:
|
|
61
61
|
|
|
62
62
|
```text
|
|
63
|
-
You: Use releasekit-draft to draft 1.4.0
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
You: Use releasekit-draft to draft 1.4.0.
|
|
64
|
+
AI: I'll use English for the original notes. Add Korean, your current
|
|
65
|
+
language, as a translation (recommended), or use English only?
|
|
66
|
+
You can also enter additional languages together.
|
|
67
|
+
You: Korean and Japanese translations.
|
|
68
|
+
AI: Found v1.4.0 and its preceding release tag v1.3.0 on this line.
|
|
69
|
+
Created releasekit/releases/1.4.0/
|
|
70
|
+
✓ Pinned v1.3.0 → v1.4.0 and collected change evidence
|
|
71
|
+
✓ Wrote English originals with Korean and Japanese translations
|
|
72
|
+
Next: use releasekit-image for the images required by these notes.
|
|
73
|
+
Choose: Prepare images (recommended), Review copy, or Stop for now.
|
|
74
|
+
|
|
75
|
+
You: Prepare images.
|
|
70
76
|
AI: ✓ Generated dark/light explanations from shared scene briefs
|
|
71
77
|
✓ Reviewed and imported both variants for each generated graphic
|
|
72
78
|
The product-detail image is pending; I need an approved capture
|
|
73
|
-
or photo.
|
|
79
|
+
or photo. Next: provide that image, or review the completed copy
|
|
80
|
+
while it is pending.
|
|
74
81
|
|
|
75
82
|
You: Use ./approved-capture.png for the product-detail note.
|
|
76
83
|
AI: ✓ Reviewed and imported it once for both viewer themes
|
|
84
|
+
Next: use releasekit-finalize to check and confirm 1.4.0.
|
|
77
85
|
|
|
78
|
-
You:
|
|
86
|
+
You: Use releasekit-finalize for 1.4.0, then export up to three releases
|
|
79
87
|
in English to ./release-output.
|
|
80
88
|
AI: ✓ Validated notes, translations, and images
|
|
81
89
|
✓ Marked release 1.4.0 ready
|
|
@@ -84,22 +92,54 @@ AI: ✓ Validated notes, translations, and images
|
|
|
84
92
|
└── assets/ ← Theme variants and shared supplied images
|
|
85
93
|
```
|
|
86
94
|
|
|
95
|
+
The agent resolves the version and Git range from your request, saved releases, repository tags, and release metadata. It reports a clear scope and proceeds without a tag-selection or confirmation step. If inspection leaves materially different scopes, it asks which work to cover in ordinary language. You can still supply explicit refs to select a particular interval.
|
|
96
|
+
|
|
97
|
+
The draft skill defaults the original language to English and suggests the user's current language for optional translation. Its question accepts additional language names or locale codes through free-text input, as well as an English-only choice. An English-speaking user is not offered a duplicate English translation. Explicit source choices and existing release selections are reused. The selection is saved for that release; future project defaults change only when requested. The draft includes the source and those selected translations. Use the same draft skill later to add a language or refresh translations.
|
|
98
|
+
|
|
87
99
|
Invoke the skill with `$releasekit-draft` in Codex, `/releasekit-draft` in Claude Code, or the skill picker in Cursor. The agent runs the CLI, generates flat explanations, and requests approved source images when the actual product or content must be shown.
|
|
88
100
|
|
|
101
|
+
## Adopting ReleaseKit later
|
|
102
|
+
|
|
103
|
+
You can start after your product has already shipped. Ask the draft skill to set a starting point; it finds a relevant release tag or commit from the repository and asks how to handle the earlier period when that choice is unresolved. Regular notes begin **after** the selected baseline commit.
|
|
104
|
+
|
|
105
|
+
| Earlier history | Result |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| **Product introduction** (recommended for an established product) | A concise overview of capabilities at the baseline, grounded in that snapshot without reconstructing every old commit. |
|
|
108
|
+
| **Analyze history** | Notes based on the repository's beginning through the baseline. |
|
|
109
|
+
| **Skip** | No earlier entry; start recording subsequent changes. |
|
|
110
|
+
|
|
111
|
+
An introduction does not assume that adopting ReleaseKit was the product's launch. Version, date, and product claims should reflect the actual product. The agent reuses choices already made in the conversation or saved setup.
|
|
112
|
+
|
|
113
|
+
For example, introduce the product at `v1.3.0`, then record changes from there:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
releasekit start --at v1.3.0 --past summary --baseline-version 1.3.0
|
|
117
|
+
releasekit prepare 1.3.0
|
|
118
|
+
# Ask the agent to write, review, and finalize the baseline introduction.
|
|
119
|
+
releasekit prepare 1.4.0 --previous 1.3.0 --to v1.4.0
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Use `--past history` to analyze earlier commits instead. With `--past skip`, omit `--baseline-version`; the first regular `prepare` uses the saved boundary. You can save `HEAD` as the start now and prepare the first draft when later commits exist. Setup pins the SHA, so moving a tag or adding commits cannot shift the boundary.
|
|
123
|
+
|
|
124
|
+
`start` saves the choice and creates no notes. Summary/history baselines become ordinary draft releases when prepared and follow the same draft (including translations), image, and finalization workflow. They count as one release in exported history; skipping creates no extra group. Existing releases keep their current workflow. See the [first-use guide](kit/references/adoption.md).
|
|
125
|
+
|
|
89
126
|
## Workflow
|
|
90
127
|
|
|
91
128
|
```text
|
|
92
|
-
Git range →
|
|
129
|
+
Git range → Draft source + translations → Images → Finalize → Optional export
|
|
93
130
|
```
|
|
94
131
|
|
|
95
132
|
| Skill | Purpose |
|
|
96
133
|
| --- | --- |
|
|
97
|
-
| `releasekit-draft` |
|
|
98
|
-
| `releasekit-image` | Plan, generate or request, review, and import images. |
|
|
99
|
-
| `releasekit-
|
|
100
|
-
| `releasekit-review` | Review content, evidence, images, and release readiness. |
|
|
134
|
+
| `releasekit-draft` | Choose a first-use baseline, write and revise source notes and selected translations, or refresh translations alone. |
|
|
135
|
+
| `releasekit-image` | Plan, generate or request, review, and import required images. |
|
|
136
|
+
| `releasekit-finalize` | Review copy, evidence, translations, and images; validate and mark the release ready; export when requested. |
|
|
101
137
|
|
|
102
|
-
|
|
138
|
+
All three skills use the agent's native question picker when clarification is needed and the tool is available, with free-text input for another answer; otherwise they ask in chat. This covers language choices, image references, translation scope or terminology, and unresolved finalization/export choices. Existing decisions are reused, and routine technical parameters are resolved through repository inspection. Required CLI flags do not become a questionnaire. Once a question is asked, dependent work waits for your submitted answer; a default selection, elapsed time, or a closed picker does not count as a choice. The agent keeps an asynchronous picker open while waiting, or asks in chat if the environment cannot support that wait. Image uploads are requested through the conversation's attachment flow.
|
|
139
|
+
|
|
140
|
+
After each stage, the agent reports what is complete and recommends the next useful task based on the release's current state. Choose a suggested action or describe another direction to continue in the same conversation; you can also stop for now. If you already requested the remaining work, the agent continues without asking again. When source and translations are complete, the next step is images; when required images are complete, it is finalization. Text-only releases go directly to finalization. Completed steps are skipped, and missing images or translations stay visible until resolved.
|
|
141
|
+
|
|
142
|
+
The agent handles editorial work, media selection, and image generation where appropriate. The CLI handles files, evidence, validation, and export. Finalization includes review and marks local content ready with a content fingerprint. Export is optional; committing, publishing, and displaying it remain separate steps.
|
|
103
143
|
|
|
104
144
|
<details>
|
|
105
145
|
<summary><strong>Step-by-step CLI workflow</strong></summary>
|
|
@@ -108,9 +148,15 @@ Replace the sample version, Git refs, and note ID with your own. If release `1.3
|
|
|
108
148
|
|
|
109
149
|
```sh
|
|
110
150
|
releasekit prepare 1.4.0 --from v1.3.0 --to v1.4.0
|
|
151
|
+
# Save selected languages in release.yaml before adding notes.
|
|
152
|
+
# This example uses sourceLocale: en-US and locales: [en-US, ko-KR].
|
|
111
153
|
releasekit note add 1.4.0 queue-action
|
|
112
154
|
|
|
113
|
-
#
|
|
155
|
+
# Write the source and selected translations, then attach evidence.
|
|
156
|
+
# Review each translation before marking it current.
|
|
157
|
+
releasekit translation mark 1.4.0 queue-action --locale ko-KR
|
|
158
|
+
|
|
159
|
+
# Complete the visual brief for image work.
|
|
114
160
|
# Choose an archetype and set scene.source to generated for this example.
|
|
115
161
|
releasekit image plan 1.4.0
|
|
116
162
|
|
|
@@ -122,15 +168,16 @@ releasekit image plan 1.4.0
|
|
|
122
168
|
# Generate and review the matching light image, then import it.
|
|
123
169
|
releasekit image import 1.4.0 queue-action --theme light --file ./selected-light.png
|
|
124
170
|
|
|
125
|
-
# Review
|
|
126
|
-
releasekit translation mark 1.4.0 queue-action --locale en-US
|
|
171
|
+
# Review facts, copy, translations, and selected images, then finalize.
|
|
127
172
|
releasekit validate 1.4.0
|
|
128
173
|
releasekit finalize 1.4.0
|
|
129
|
-
|
|
174
|
+
# Export when requested.
|
|
175
|
+
releasekit export --current 1.4.0 --limit 3 --locale en-US --out ./release-output
|
|
130
176
|
```
|
|
131
177
|
|
|
178
|
+
- Preparing creates only `release.yaml` with pinned Git boundaries. The agent reads commit history and relevant file diffs from Git as needed.
|
|
132
179
|
- Use `--from-root` for an explicitly requested full-history first release.
|
|
133
|
-
- `--to` defaults to `HEAD
|
|
180
|
+
- `--to` defaults to the pinned SHA when preparing a saved baseline, and to `HEAD` otherwise; `--previous` can supply the comparison start.
|
|
134
181
|
- Edit existing drafts in place. `prepare` never overwrites them.
|
|
135
182
|
- Use `note add --no-image` for an intentionally text-only note.
|
|
136
183
|
- Add `--json` for structured results or `--cwd` to select a project directory.
|
|
@@ -215,14 +262,14 @@ releasekit/
|
|
|
215
262
|
└── releases/
|
|
216
263
|
└── 1.4.0/
|
|
217
264
|
├── release.yaml # Metadata, note order, previous release
|
|
218
|
-
├── evidence.json # Pinned Git evidence
|
|
219
|
-
├── changes.patch # Net change for this release
|
|
220
265
|
├── notes/ # Markdown for each locale
|
|
221
266
|
├── visuals/ # Scene briefs, media sources, and variants
|
|
222
267
|
├── prompts/ # Prompts for pending generated variants
|
|
223
268
|
└── assets/ # Selected raster images
|
|
224
269
|
```
|
|
225
270
|
|
|
271
|
+
Releases store the comparison start and end SHAs in `release.yaml`, with relevant paths or commits attached to individual notes. They do not save a full patch or a separate changed-file index. Draft validation reads the pinned Git range, or the baseline snapshot for a product introduction; finalized releases can be validated and exported without Git history.
|
|
272
|
+
|
|
226
273
|
Export follows explicit `previous` links, keeping each version's notes in a separate group. The default limit is **three releases, including the current one**. Similar notes in different versions remain separate.
|
|
227
274
|
|
|
228
275
|
The bundle contains display data and relative assets. Git evidence, prompts, and private source paths stay out of the export. Consumers safely render `bodyMarkdown` and select `image.variants[theme]`, falling back to `image.variants[image.fallbackTheme]` when needed. Text-only notes have `image: null`.
|
|
@@ -235,7 +282,8 @@ Translations track source fingerprints, and finalized releases record content fi
|
|
|
235
282
|
|
|
236
283
|
| Guide | What it covers |
|
|
237
284
|
| --- | --- |
|
|
238
|
-
| [
|
|
285
|
+
| [First use in an existing product](kit/references/adoption.md) | Starting points, product introductions, historical analysis, and skipped history. |
|
|
286
|
+
| [Agent workflow](kit/references/workflow.md) | Git boundaries, drafts and translations, images, finalization, and export. |
|
|
239
287
|
| [Writing and translation](kit/references/writing.md) | Product copy, evidence, and locale freshness. |
|
|
240
288
|
| [Visual language](kit/references/visual-language.md) | Composition, hierarchy, materials, and acceptance checks. |
|
|
241
289
|
| [Choosing generated or supplied media](kit/references/media-sources.md) | Source selection, pending captures, and shared assets. |
|
|
@@ -260,7 +308,7 @@ npm run build
|
|
|
260
308
|
npm pack --dry-run
|
|
261
309
|
```
|
|
262
310
|
|
|
263
|
-
CI runs on Windows and Linux with Node.js 22 and 24. Tests cover Git ranges, release history, image integrity, theme policies, supplied media and shared assets, translation freshness, finalization, installation conflicts, and CLI behavior.
|
|
311
|
+
CI runs on Windows and Linux with Node.js 22 and 24. Tests cover first-use setup, snapshot summaries, Git ranges, release history, image integrity, theme policies, supplied media and shared assets, translation freshness, finalization, installation conflicts, and CLI behavior.
|
|
264
312
|
|
|
265
313
|
<details>
|
|
266
314
|
<summary><strong>Install from a local checkout</strong></summary>
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
import { Command, Option } from 'commander';
|
|
5
|
-
import { Project, prepare } from './project.js';
|
|
5
|
+
import { Project, prepare, startProject } from './project.js';
|
|
6
6
|
import { initProject, installSkills } from './install.js';
|
|
7
7
|
import { addNote, markTranslation, syncImagePolicy } from './content.js';
|
|
8
8
|
import { planImages, importImage } from './images.js';
|
|
@@ -29,9 +29,14 @@ program.command('init').description('Initialize content and install project skil
|
|
|
29
29
|
program.command('update').description('Refresh managed skills while preserving user edits')
|
|
30
30
|
.action(async () => { const result = await installSkills(project()); emit(result); if (result.conflicts.length)
|
|
31
31
|
process.exitCode = 1; });
|
|
32
|
+
program.command('start').description('Save the first-use Git boundary and treatment of earlier history')
|
|
33
|
+
.requiredOption('--at <ref>', 'baseline commit or tag; subsequent notes begin after this commit')
|
|
34
|
+
.addOption(new Option('--past <mode>', 'summarize the baseline, analyze history, or skip earlier notes').choices(['summary', 'history', 'skip']).makeOptionMandatory())
|
|
35
|
+
.option('--baseline-version <version>', 'baseline release ID, required for summary or history')
|
|
36
|
+
.action(async (options) => emit(await startProject(project(), { at: options.at, past: options.past, version: options.baselineVersion })));
|
|
32
37
|
program.command('prepare <version>').description('Create a draft from pinned Git commits')
|
|
33
38
|
.option('--from <ref>', 'comparison start commit or tag')
|
|
34
|
-
.option('--to <ref>', 'comparison end commit or tag
|
|
39
|
+
.option('--to <ref>', 'comparison end commit or tag, defaults to the saved baseline or HEAD')
|
|
35
40
|
.option('--previous <version>', 'explicit previous release')
|
|
36
41
|
.option('--from-root', 'explicitly include the whole history')
|
|
37
42
|
.option('--first-release', 'start an independent release line')
|
package/dist/git.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { Release } from './model.js';
|
|
2
|
+
interface GitChanges {
|
|
3
|
+
source: Release['source'];
|
|
4
|
+
commits: Array<{
|
|
5
|
+
sha: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
}>;
|
|
8
|
+
files: Array<{
|
|
9
|
+
status: string;
|
|
10
|
+
path: string;
|
|
11
|
+
oldPath?: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
2
14
|
export declare function git(cwd: string, args: string[], input?: string): string;
|
|
3
15
|
export declare function repoRoot(cwd: string): string;
|
|
4
16
|
export declare function resolveCommit(root: string, ref: string): string;
|
|
5
17
|
export declare function isAncestor(root: string, base: string, head: string): boolean;
|
|
6
|
-
export declare function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export
|
|
18
|
+
export declare function resolveRange(root: string, from: string | null, to: string): Release['source'];
|
|
19
|
+
export declare function collect(root: string, from: string | null, to: string): GitChanges;
|
|
20
|
+
export declare function collectSnapshot(root: string, to: string): GitChanges;
|
|
21
|
+
export declare function checkPrevious(root: string, previous: Release, source: Release['source']): void;
|
|
22
|
+
export {};
|
package/dist/git.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { evidenceSchema } from './model.js';
|
|
3
2
|
export function git(cwd, args, input) {
|
|
4
3
|
const result = spawnSync('git', args, {
|
|
5
4
|
cwd, input, encoding: 'utf8', windowsHide: true, maxBuffer: 64 * 1024 * 1024,
|
|
@@ -27,7 +26,7 @@ export function isAncestor(root, base, head) {
|
|
|
27
26
|
return false;
|
|
28
27
|
throw new Error(`Cannot establish Git ancestry: ${result.stderr || result.error?.message}`);
|
|
29
28
|
}
|
|
30
|
-
export function
|
|
29
|
+
export function resolveRange(root, from, to) {
|
|
31
30
|
if (git(root, ['rev-parse', '--is-shallow-repository']).trim() === 'true') {
|
|
32
31
|
throw new Error('Complete the shallow Git history before preparing a release; no fetch was performed.');
|
|
33
32
|
}
|
|
@@ -38,6 +37,11 @@ export function collect(root, from, to) {
|
|
|
38
37
|
if (fromSha && !isAncestor(root, fromSha, toSha)) {
|
|
39
38
|
throw new Error('The start commit is not an ancestor of the end commit. Choose an explicit range on this release line.');
|
|
40
39
|
}
|
|
40
|
+
return { fromRef: from, fromSha, toRef: to, toSha };
|
|
41
|
+
}
|
|
42
|
+
export function collect(root, from, to) {
|
|
43
|
+
const source = resolveRange(root, from, to);
|
|
44
|
+
const { fromSha, toSha } = source;
|
|
41
45
|
const base = fromSha ?? git(root, ['hash-object', '-t', 'tree', '--stdin'], '').trim();
|
|
42
46
|
const log = git(root, ['log', '--no-show-signature', '--format=%H%x00%s', fromSha ? `${fromSha}..${toSha}` : toSha, '--']);
|
|
43
47
|
const commits = log.trimEnd() ? log.trimEnd().split('\n').map(line => {
|
|
@@ -54,14 +58,19 @@ export function collect(root, from, to) {
|
|
|
54
58
|
else
|
|
55
59
|
files.push({ status, path: first });
|
|
56
60
|
}
|
|
57
|
-
|
|
61
|
+
return { source, commits, files };
|
|
62
|
+
}
|
|
63
|
+
export function collectSnapshot(root, to) {
|
|
64
|
+
const source = resolveRange(root, null, to);
|
|
65
|
+
const subject = git(root, ['show', '--no-show-signature', '--no-patch', '--format=%s', source.toSha, '--']).trimEnd();
|
|
66
|
+
const paths = git(root, ['ls-tree', '-r', '--name-only', '-z', source.toSha, '--']);
|
|
58
67
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
source, commits: [{ sha: source.toSha, subject }],
|
|
69
|
+
files: paths.split('\0').filter(Boolean).map(path => ({ status: 'A', path })),
|
|
61
70
|
};
|
|
62
71
|
}
|
|
63
|
-
export function checkPrevious(root, previous,
|
|
64
|
-
const boundary =
|
|
72
|
+
export function checkPrevious(root, previous, source) {
|
|
73
|
+
const boundary = source.fromSha;
|
|
65
74
|
if (!boundary || !isAncestor(root, previous.source.toSha, boundary)) {
|
|
66
75
|
throw new Error('The previous release is not an ancestor of this comparison start. Choose the correct previous release.');
|
|
67
76
|
}
|
package/dist/install.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Project } from './project.js';
|
|
|
6
6
|
import { configSchema, defaultConfig } from './model.js';
|
|
7
7
|
import { exists, within, write, writeYaml, digest } from './files.js';
|
|
8
8
|
const resources = fileURLToPath(new URL('../kit/', import.meta.url));
|
|
9
|
-
const names = ['releasekit-draft', 'releasekit-image', 'releasekit-
|
|
9
|
+
const names = ['releasekit-draft', 'releasekit-image', 'releasekit-finalize'];
|
|
10
10
|
const managedSchema = z.record(z.string(), z.string().regex(/^[a-f0-9]{64}$/));
|
|
11
11
|
export async function installSkills(project) {
|
|
12
12
|
const config = await project.config();
|
|
@@ -44,8 +44,8 @@ export async function installSkills(project) {
|
|
|
44
44
|
}
|
|
45
45
|
await write(marker, JSON.stringify(managed, null, 2) + '\n');
|
|
46
46
|
return { tools: config.tools, written, conflicts, hints: {
|
|
47
|
-
codex: 'Use $releasekit-draft, $releasekit-image,
|
|
48
|
-
claude: 'Use /releasekit-draft, /releasekit-image,
|
|
47
|
+
codex: 'Use $releasekit-draft, $releasekit-image, or $releasekit-finalize.',
|
|
48
|
+
claude: 'Use /releasekit-draft, /releasekit-image, or /releasekit-finalize.',
|
|
49
49
|
cursor: 'Use the installed releasekit-* skills from the agent skill picker or name them in your request.',
|
|
50
50
|
} };
|
|
51
51
|
}
|
package/dist/model.d.ts
CHANGED
|
@@ -49,6 +49,23 @@ export declare const visualPolicySchema: z.ZodObject<{
|
|
|
49
49
|
}, z.core.$strict>;
|
|
50
50
|
}, z.core.$strict>;
|
|
51
51
|
export type VisualPolicy = z.infer<typeof visualPolicySchema>;
|
|
52
|
+
export declare const historyStartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
53
|
+
ref: z.ZodString;
|
|
54
|
+
sha: z.ZodString;
|
|
55
|
+
past: z.ZodLiteral<"summary">;
|
|
56
|
+
version: z.ZodString;
|
|
57
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
58
|
+
ref: z.ZodString;
|
|
59
|
+
sha: z.ZodString;
|
|
60
|
+
past: z.ZodLiteral<"history">;
|
|
61
|
+
version: z.ZodString;
|
|
62
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
63
|
+
ref: z.ZodString;
|
|
64
|
+
sha: z.ZodString;
|
|
65
|
+
past: z.ZodLiteral<"skip">;
|
|
66
|
+
version: z.ZodNull;
|
|
67
|
+
}, z.core.$strict>], "past">;
|
|
68
|
+
export type HistoryStart = z.infer<typeof historyStartSchema>;
|
|
52
69
|
export declare const configSchema: z.ZodObject<{
|
|
53
70
|
schemaVersion: z.ZodLiteral<1>;
|
|
54
71
|
product: z.ZodString;
|
|
@@ -56,6 +73,22 @@ export declare const configSchema: z.ZodObject<{
|
|
|
56
73
|
locales: z.ZodArray<z.ZodString>;
|
|
57
74
|
history: z.ZodObject<{
|
|
58
75
|
limit: z.ZodNumber;
|
|
76
|
+
start: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
77
|
+
ref: z.ZodString;
|
|
78
|
+
sha: z.ZodString;
|
|
79
|
+
past: z.ZodLiteral<"summary">;
|
|
80
|
+
version: z.ZodString;
|
|
81
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
82
|
+
ref: z.ZodString;
|
|
83
|
+
sha: z.ZodString;
|
|
84
|
+
past: z.ZodLiteral<"history">;
|
|
85
|
+
version: z.ZodString;
|
|
86
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
87
|
+
ref: z.ZodString;
|
|
88
|
+
sha: z.ZodString;
|
|
89
|
+
past: z.ZodLiteral<"skip">;
|
|
90
|
+
version: z.ZodNull;
|
|
91
|
+
}, z.core.$strict>], "past">>;
|
|
59
92
|
}, z.core.$strict>;
|
|
60
93
|
visuals: z.ZodObject<{
|
|
61
94
|
themes: z.ZodEnum<{
|
|
@@ -125,6 +158,10 @@ export declare const releaseSchema: z.ZodObject<{
|
|
|
125
158
|
toRef: z.ZodString;
|
|
126
159
|
toSha: z.ZodString;
|
|
127
160
|
}, z.core.$strict>;
|
|
161
|
+
initialContent: z.ZodOptional<z.ZodEnum<{
|
|
162
|
+
history: "history";
|
|
163
|
+
summary: "summary";
|
|
164
|
+
}>>;
|
|
128
165
|
sourceLocale: z.ZodString;
|
|
129
166
|
locales: z.ZodArray<z.ZodString>;
|
|
130
167
|
visuals: z.ZodObject<{
|
|
@@ -275,25 +312,6 @@ export declare const visualSchema: z.ZodObject<{
|
|
|
275
312
|
}, z.core.$strict>;
|
|
276
313
|
}, z.core.$strict>;
|
|
277
314
|
export type Visual = z.infer<typeof visualSchema>;
|
|
278
|
-
export declare const evidenceSchema: z.ZodObject<{
|
|
279
|
-
schemaVersion: z.ZodLiteral<1>;
|
|
280
|
-
source: z.ZodObject<{
|
|
281
|
-
fromRef: z.ZodNullable<z.ZodString>;
|
|
282
|
-
fromSha: z.ZodNullable<z.ZodString>;
|
|
283
|
-
toRef: z.ZodString;
|
|
284
|
-
toSha: z.ZodString;
|
|
285
|
-
}, z.core.$strict>;
|
|
286
|
-
commits: z.ZodArray<z.ZodObject<{
|
|
287
|
-
sha: z.ZodString;
|
|
288
|
-
subject: z.ZodString;
|
|
289
|
-
}, z.core.$strict>>;
|
|
290
|
-
files: z.ZodArray<z.ZodObject<{
|
|
291
|
-
status: z.ZodString;
|
|
292
|
-
path: z.ZodString;
|
|
293
|
-
oldPath: z.ZodOptional<z.ZodString>;
|
|
294
|
-
}, z.core.$strict>>;
|
|
295
|
-
}, z.core.$strict>;
|
|
296
|
-
export type Evidence = z.infer<typeof evidenceSchema>;
|
|
297
315
|
export declare const bundleSchema: z.ZodObject<{
|
|
298
316
|
schemaVersion: z.ZodLiteral<1>;
|
|
299
317
|
currentVersion: z.ZodString;
|
package/dist/model.js
CHANGED
|
@@ -17,10 +17,15 @@ export const visualPolicySchema = z.strictObject({
|
|
|
17
17
|
dark: paletteSchema,
|
|
18
18
|
light: paletteSchema,
|
|
19
19
|
});
|
|
20
|
+
export const historyStartSchema = z.discriminatedUnion('past', [
|
|
21
|
+
z.strictObject({ ref: z.string().min(1), sha, past: z.literal('summary'), version: segment }),
|
|
22
|
+
z.strictObject({ ref: z.string().min(1), sha, past: z.literal('history'), version: segment }),
|
|
23
|
+
z.strictObject({ ref: z.string().min(1), sha, past: z.literal('skip'), version: z.null() }),
|
|
24
|
+
]);
|
|
20
25
|
export const configSchema = z.strictObject({
|
|
21
26
|
schemaVersion: z.literal(1), product: z.string().min(1),
|
|
22
27
|
sourceLocale: locale, locales: z.array(locale).min(1),
|
|
23
|
-
history: z.strictObject({ limit: z.number().int().min(1).max(100) }),
|
|
28
|
+
history: z.strictObject({ limit: z.number().int().min(1).max(100), start: historyStartSchema.optional() }),
|
|
24
29
|
visuals: visualPolicySchema,
|
|
25
30
|
tools: z.array(z.enum(['codex', 'claude', 'cursor'])),
|
|
26
31
|
});
|
|
@@ -35,7 +40,8 @@ export const noteMetaSchema = z.strictObject({
|
|
|
35
40
|
export const releaseSchema = z.strictObject({
|
|
36
41
|
schemaVersion: z.literal(1), version: segment, releasedAt: z.iso.date(),
|
|
37
42
|
previous: segment.nullable(), status: z.enum(['draft', 'ready']),
|
|
38
|
-
source: sourceSchema,
|
|
43
|
+
source: sourceSchema, initialContent: z.enum(['summary', 'history']).optional(),
|
|
44
|
+
sourceLocale: locale, locales: z.array(locale).min(1),
|
|
39
45
|
visuals: visualPolicySchema, notes: z.array(noteMetaSchema),
|
|
40
46
|
emptyReason: z.string().nullable(), contentHash: z.string().nullable(),
|
|
41
47
|
});
|
|
@@ -64,11 +70,6 @@ export const visualSchema = z.strictObject({
|
|
|
64
70
|
schemaVersion: z.literal(1), scene: sceneSchema,
|
|
65
71
|
variants: z.strictObject({ dark: assetSchema.optional(), light: assetSchema.optional(), shared: assetSchema.optional() }),
|
|
66
72
|
});
|
|
67
|
-
export const evidenceSchema = z.strictObject({
|
|
68
|
-
schemaVersion: z.literal(1), source: sourceSchema,
|
|
69
|
-
commits: z.array(z.strictObject({ sha, subject: z.string() })),
|
|
70
|
-
files: z.array(z.strictObject({ status: z.string(), path: z.string(), oldPath: z.string().optional() })),
|
|
71
|
-
});
|
|
72
73
|
const exportedImage = z.strictObject({
|
|
73
74
|
src: z.string(), width: z.number().int().positive(), height: z.number().int().positive(),
|
|
74
75
|
});
|
|
@@ -112,7 +113,7 @@ export function activeVariants(visual, policy) {
|
|
|
112
113
|
}
|
|
113
114
|
export function defaultConfig(product) {
|
|
114
115
|
return {
|
|
115
|
-
schemaVersion: 1, product, sourceLocale: '
|
|
116
|
+
schemaVersion: 1, product, sourceLocale: 'en-US', locales: ['en-US'],
|
|
116
117
|
history: { limit: 3 }, tools: ['codex', 'claude', 'cursor'],
|
|
117
118
|
visuals: {
|
|
118
119
|
themes: 'both', preset: 'quiet-product', width: 1280, height: 800, accent: '#4678ED',
|
package/dist/project.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type HistoryStart, type ProjectConfig, type Release } from './model.js';
|
|
2
2
|
export declare class Project {
|
|
3
3
|
readonly root: string;
|
|
4
4
|
constructor(root: string);
|
|
@@ -10,10 +10,15 @@ export declare class Project {
|
|
|
10
10
|
release(version: string): Promise<Release>;
|
|
11
11
|
save(release: Release): Promise<void>;
|
|
12
12
|
versions(): Promise<string[]>;
|
|
13
|
-
evidence(version: string): Promise<Evidence>;
|
|
14
13
|
history(version: string, limit: number): Promise<Release[]>;
|
|
15
14
|
}
|
|
16
15
|
export declare function editable(release: Release): void;
|
|
16
|
+
export interface StartOptions {
|
|
17
|
+
at: string;
|
|
18
|
+
past: HistoryStart['past'];
|
|
19
|
+
version?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function startProject(project: Project, options: StartOptions): Promise<HistoryStart>;
|
|
17
22
|
export interface PrepareOptions {
|
|
18
23
|
from?: string;
|
|
19
24
|
to?: string;
|
package/dist/project.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { configSchema,
|
|
4
|
-
import { exists, identifier, readYaml, within, writeYaml,
|
|
5
|
-
import { repoRoot,
|
|
3
|
+
import { configSchema, historyStartSchema, releaseSchema } from './model.js';
|
|
4
|
+
import { exists, identifier, readYaml, within, writeYaml, KIT_DIR } from './files.js';
|
|
5
|
+
import { repoRoot, resolveCommit, resolveRange, checkPrevious } from './git.js';
|
|
6
6
|
export class Project {
|
|
7
7
|
root;
|
|
8
8
|
constructor(root) { this.root = path.resolve(root); }
|
|
@@ -37,9 +37,6 @@ export class Project {
|
|
|
37
37
|
const entries = await fs.readdir(folder, { withFileTypes: true });
|
|
38
38
|
return entries.filter(e => e.isDirectory()).map(e => e.name).sort();
|
|
39
39
|
}
|
|
40
|
-
async evidence(version) {
|
|
41
|
-
return evidenceSchema.parse(JSON.parse(await fs.readFile(await this.releaseFile(version, 'evidence.json'), 'utf8')));
|
|
42
|
-
}
|
|
43
40
|
async history(version, limit) {
|
|
44
41
|
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
45
42
|
throw new Error('History limit must be an integer from 1 to 100.');
|
|
@@ -63,6 +60,24 @@ export function editable(release) {
|
|
|
63
60
|
if (release.status !== 'draft')
|
|
64
61
|
throw new Error('This release is ready. Set status to draft and contentHash to null before editing it.');
|
|
65
62
|
}
|
|
63
|
+
export async function startProject(project, options) {
|
|
64
|
+
const config = await project.config();
|
|
65
|
+
if (config.history.start)
|
|
66
|
+
throw new Error('A history start is already configured. Reuse the saved choice; it was not overwritten.');
|
|
67
|
+
if ((await project.versions()).length)
|
|
68
|
+
throw new Error('History setup requires a project without releases. Continue an existing release line with --previous.');
|
|
69
|
+
if (options.past === 'skip' && options.version !== undefined)
|
|
70
|
+
throw new Error('--baseline-version is not used when --past is skip.');
|
|
71
|
+
if (options.past !== 'skip' && !options.version)
|
|
72
|
+
throw new Error('Specify --baseline-version for the baseline summary or history release.');
|
|
73
|
+
if (options.version)
|
|
74
|
+
identifier(options.version);
|
|
75
|
+
const source = resolveRange(project.root, null, options.at);
|
|
76
|
+
const start = historyStartSchema.parse({ ref: options.at, sha: source.toSha, past: options.past, version: options.version ?? null });
|
|
77
|
+
config.history.start = start;
|
|
78
|
+
await writeYaml(await project.content('config.yaml'), config);
|
|
79
|
+
return start;
|
|
80
|
+
}
|
|
66
81
|
export async function prepare(project, version, options) {
|
|
67
82
|
identifier(version);
|
|
68
83
|
const directory = await project.releaseDir(version);
|
|
@@ -73,31 +88,62 @@ export async function prepare(project, version, options) {
|
|
|
73
88
|
throw new Error('--from-root cannot be combined with --from or --previous.');
|
|
74
89
|
if (options.firstRelease && options.previous)
|
|
75
90
|
throw new Error('--first-release cannot be combined with --previous.');
|
|
91
|
+
const versions = await project.versions();
|
|
92
|
+
const start = config.history.start;
|
|
76
93
|
let previous = options.previous ? await project.release(options.previous) : undefined;
|
|
77
|
-
|
|
94
|
+
let from = options.fromRoot ? null : options.from ?? previous?.source.toSha;
|
|
95
|
+
let to = options.to ?? 'HEAD';
|
|
96
|
+
let initialContent;
|
|
97
|
+
let savedStart = false;
|
|
98
|
+
if (start && start.past !== 'skip' && version === start.version) {
|
|
99
|
+
if (options.from !== undefined || options.previous !== undefined)
|
|
100
|
+
throw new Error('The configured baseline starts at the root and has no previous release.');
|
|
101
|
+
if (options.to !== undefined && resolveCommit(project.root, options.to) !== start.sha)
|
|
102
|
+
throw new Error('The requested end differs from the pinned history start.');
|
|
103
|
+
from = null;
|
|
104
|
+
to = start.sha;
|
|
105
|
+
initialContent = start.past;
|
|
106
|
+
}
|
|
107
|
+
else if (start && from === undefined && !options.firstRelease) {
|
|
108
|
+
if (start.past === 'skip' && !versions.length) {
|
|
109
|
+
from = start.sha;
|
|
110
|
+
savedStart = true;
|
|
111
|
+
}
|
|
112
|
+
else if (start.past !== 'skip' && versions.every(v => v === start.version)) {
|
|
113
|
+
if (!versions.includes(start.version))
|
|
114
|
+
throw new Error(`Prepare the configured baseline ${start.version} first, then continue with --previous ${start.version}.`);
|
|
115
|
+
previous = await project.release(start.version);
|
|
116
|
+
if (previous.source.toSha !== start.sha || previous.initialContent !== start.past)
|
|
117
|
+
throw new Error('The baseline release differs from the saved history start. Choose an explicit --previous release.');
|
|
118
|
+
from = previous.source.toSha;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
78
121
|
if (from === undefined)
|
|
79
|
-
throw new Error('Specify --from, --previous, or --from-root.');
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
122
|
+
throw new Error('Specify --from, --previous, or --from-root, or configure a first-use boundary with start.');
|
|
123
|
+
const source = resolveRange(project.root, from, to);
|
|
124
|
+
if (initialContent && start)
|
|
125
|
+
source.toRef = start.ref;
|
|
126
|
+
if (savedStart && start)
|
|
127
|
+
source.fromRef = start.ref;
|
|
128
|
+
if (!previous && !options.fromRoot && !options.firstRelease && !initialContent) {
|
|
129
|
+
const existing = await Promise.all(versions.map(v => project.release(v)));
|
|
130
|
+
const candidates = existing.filter(r => r.source.toSha === source.fromSha);
|
|
84
131
|
if (candidates.length === 1)
|
|
85
132
|
previous = candidates[0];
|
|
86
133
|
else if (existing.length)
|
|
87
134
|
throw new Error('Previous release is ambiguous. Specify --previous, or --first-release for an independent release line.');
|
|
88
135
|
}
|
|
89
136
|
if (previous) {
|
|
90
|
-
checkPrevious(project.root, previous,
|
|
137
|
+
checkPrevious(project.root, previous, source);
|
|
91
138
|
await project.history(previous.version, 1);
|
|
92
139
|
}
|
|
93
140
|
const release = releaseSchema.parse({
|
|
94
141
|
schemaVersion: 1, version, releasedAt: options.date ?? new Date().toISOString().slice(0, 10),
|
|
95
|
-
previous: previous?.version ?? null, status: 'draft', source
|
|
142
|
+
previous: previous?.version ?? null, status: 'draft', source,
|
|
143
|
+
...(initialContent ? { initialContent } : {}),
|
|
96
144
|
sourceLocale: config.sourceLocale, locales: config.locales, visuals: config.visuals,
|
|
97
145
|
notes: [], emptyReason: null, contentHash: null,
|
|
98
146
|
});
|
|
99
147
|
await project.save(release);
|
|
100
|
-
await write(await project.releaseFile(version, 'evidence.json'), JSON.stringify(evidence, null, 2) + '\n');
|
|
101
|
-
await write(await project.releaseFile(version, 'changes.patch'), patch);
|
|
102
148
|
return release;
|
|
103
149
|
}
|
package/dist/schema-export.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { configSchema, releaseSchema, visualSchema, noteTextSchema,
|
|
4
|
+
import { configSchema, releaseSchema, visualSchema, noteTextSchema, bundleSchema } from './model.js';
|
|
5
5
|
const directory = fileURLToPath(new URL('../schemas/', import.meta.url));
|
|
6
6
|
await fs.mkdir(directory, { recursive: true });
|
|
7
|
-
|
|
7
|
+
const schemas = { config: configSchema, release: releaseSchema, visual: visualSchema, note: noteTextSchema, bundle: bundleSchema };
|
|
8
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
8
9
|
const json = { ...z.toJSONSchema(schema), $id: `urn:releasekit:${name}:1` };
|
|
9
10
|
await fs.writeFile(`${directory}/${name}.schema.json`, JSON.stringify(json, null, 2) + '\n');
|
|
10
11
|
}
|
|
12
|
+
// Keep the generated directory aligned with the schemas the package exports.
|
|
13
|
+
const outputs = new Set(Object.keys(schemas).map(name => `${name}.schema.json`));
|
|
14
|
+
for (const file of await fs.readdir(directory)) {
|
|
15
|
+
if (file.endsWith('.schema.json') && !outputs.has(file))
|
|
16
|
+
await fs.unlink(`${directory}/${file}`);
|
|
17
|
+
}
|