@iodes/releasekit 0.1.3 → 0.1.4
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 +33 -11
- package/dist/assets.d.ts +10 -0
- package/dist/assets.js +61 -0
- package/dist/cli.js +10 -7
- package/dist/content.d.ts +7 -0
- package/dist/content.js +104 -13
- package/dist/export.d.ts +2 -2
- package/dist/export.js +41 -27
- package/dist/images.d.ts +4 -1
- package/dist/images.js +37 -14
- package/dist/project.d.ts +1 -0
- package/dist/project.js +13 -0
- package/kit/references/format.md +11 -1
- package/kit/references/media-sources.md +3 -3
- package/kit/references/theme-pairing.md +38 -1
- package/kit/references/visual-language.md +1 -1
- package/kit/references/workflow.md +38 -13
- package/kit/references/writing.md +32 -5
- package/kit/skills/releasekit-draft/SKILL.md +9 -3
- package/kit/skills/releasekit-finalize/SKILL.md +6 -2
- package/kit/skills/releasekit-image/SKILL.md +9 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,8 +69,18 @@ AI: Found v1.4.0 and its preceding release tag v1.3.0 on this line.
|
|
|
69
69
|
Created releasekit/releases/1.4.0/
|
|
70
70
|
✓ Pinned v1.3.0 → v1.4.0 and collected change evidence
|
|
71
71
|
✓ Wrote English originals with Korean and Japanese translations
|
|
72
|
-
Next: use releasekit-image
|
|
73
|
-
Choose: Prepare images (recommended),
|
|
72
|
+
Next: use releasekit-image to prepare images for all drafted notes.
|
|
73
|
+
Choose: Prepare images (recommended), Request copy changes,
|
|
74
|
+
or Finish drafting — save the draft and ask for revisions later.
|
|
75
|
+
|
|
76
|
+
You: Finish drafting.
|
|
77
|
+
AI: Drafting 1.4.0 is complete. Review releasekit/releases/1.4.0/.
|
|
78
|
+
Ask me here to rewrite a feature description, leave a feature out,
|
|
79
|
+
or add a feature that was missed. I'll update the draft and its
|
|
80
|
+
affected translations. Images and finalization remain pending.
|
|
81
|
+
|
|
82
|
+
You: Rewrite the queue feature description to explain when to use it.
|
|
83
|
+
AI: Updated that feature description and its Korean and Japanese translations.
|
|
74
84
|
|
|
75
85
|
You: Prepare images.
|
|
76
86
|
AI: ✓ Generated dark/light explanations from shared scene briefs
|
|
@@ -81,15 +91,17 @@ AI: ✓ Generated dark/light explanations from shared scene briefs
|
|
|
81
91
|
|
|
82
92
|
You: Use ./approved-capture.png for the product-detail note.
|
|
83
93
|
AI: ✓ Reviewed and imported it once for both viewer themes
|
|
84
|
-
|
|
94
|
+
Images for all current notes are ready to review.
|
|
95
|
+
Ask me to revise any feature image or replace it with another image.
|
|
96
|
+
If no image changes are needed, use releasekit-finalize to confirm 1.4.0.
|
|
85
97
|
|
|
86
98
|
You: Use releasekit-finalize for 1.4.0, then export up to three releases
|
|
87
99
|
in English to ./release-output.
|
|
88
100
|
AI: ✓ Validated notes, translations, and images
|
|
89
101
|
✓ Marked release 1.4.0 ready
|
|
90
102
|
Exported release-output/
|
|
91
|
-
├── release-notes.json
|
|
92
|
-
└── assets/
|
|
103
|
+
├── release-notes.en-US.json ← English notes grouped by release
|
|
104
|
+
└── assets/ ← Theme variants and shared supplied images
|
|
93
105
|
```
|
|
94
106
|
|
|
95
107
|
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.
|
|
@@ -135,9 +147,9 @@ Git range → Draft source + translations → Images → Finalize → Optional e
|
|
|
135
147
|
| `releasekit-image` | Plan, generate or request, review, and import required images. |
|
|
136
148
|
| `releasekit-finalize` | Review copy, evidence, translations, and images; validate and mark the release ready; export when requested. |
|
|
137
149
|
|
|
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.
|
|
150
|
+
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. Only one question request may remain unanswered in the conversation: newly discovered questions and next-step choices wait in a queue, even across skills or releases. A partial answer keeps the remaining questions pending. 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
151
|
|
|
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
|
|
152
|
+
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. After drafting is complete, **Finish drafting** saves the draft for you to read and request changes from the agent. The handoff includes links and examples of revision requests; the agent edits the same draft and refreshes affected translations when you ask. At other stages, you can 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
153
|
|
|
142
154
|
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.
|
|
143
155
|
|
|
@@ -171,17 +183,21 @@ releasekit image import 1.4.0 queue-action --theme light --file ./selected-light
|
|
|
171
183
|
# Review facts, copy, translations, and selected images, then finalize.
|
|
172
184
|
releasekit validate 1.4.0
|
|
173
185
|
releasekit finalize 1.4.0
|
|
186
|
+
|
|
174
187
|
# Export when requested.
|
|
175
|
-
releasekit export --
|
|
188
|
+
releasekit export --out ./release-output
|
|
176
189
|
```
|
|
177
190
|
|
|
178
191
|
- Preparing creates only `release.yaml` with pinned Git boundaries. The agent reads commit history and relevant file diffs from Git as needed.
|
|
179
192
|
- Use `--from-root` for an explicitly requested full-history first release.
|
|
180
193
|
- `--to` defaults to the pinned SHA when preparing a saved baseline, and to `HEAD` otherwise; `--previous` can supply the comparison start.
|
|
181
194
|
- Edit existing drafts in place. `prepare` never overwrites them.
|
|
182
|
-
- Use `note add --no-image` for an
|
|
195
|
+
- Notes include images by default. Use `note add --no-image` only for an explicit text-only choice. Adding a note clears any previous `emptyReason`.
|
|
196
|
+
- Use `releasekit note remove <version> <id>` to exclude a draft note. It removes the note folder, translations, visual brief, prompts, and unused managed images, including older imports. Images referenced by remaining visuals and source originals are preserved. The result lists removed paths and retained shared assets. Removing the last note leaves the draft pending until you add notes or supply a factual `emptyReason`.
|
|
183
197
|
- Add `--json` for structured results or `--cwd` to select a project directory.
|
|
184
|
-
-
|
|
198
|
+
- Only `--out` is required for export. Omit `--current` to select the release with no successor in the saved `previous` links; multiple release lines require an explicit `--current`. The selected release must be ready. Omit `--limit` to use `history.limit` from `releasekit/config.yaml` (initially 3).
|
|
199
|
+
- Omit `--locale` to export every locale saved in the current release as `release-notes.<locale>.json`, such as `release-notes.en-US.json` and `release-notes.ko-KR.json`. Add `--locale en-US` for only the English file. All files share the same `assets/` directory. Each selected version must contain the requested locales; missing or stale translations stop the export before output is created.
|
|
200
|
+
- Export to a new directory; an existing destination is never overwritten. The command result lists generated JSON paths in `files`, the number of version groups in `releases`, and the number of shared image files in `assets`.
|
|
185
201
|
|
|
186
202
|
See the [agent workflow](kit/references/workflow.md) for ancestry rules and continuing existing releases.
|
|
187
203
|
|
|
@@ -198,6 +214,10 @@ Codex and Cursor share `.agents/skills` to avoid duplicate discovery. Claude Cod
|
|
|
198
214
|
|
|
199
215
|
## Image themes
|
|
200
216
|
|
|
217
|
+
Image work covers **every drafted note** by default, including small fixes and improvements. Only an explicit text-only choice omits a note's image. Calling `releasekit-image` again fills missing images, including those for notes added later, and reuses existing valid images. Existing images that need corrections stay pending until the affected revision or replacement is requested.
|
|
218
|
+
|
|
219
|
+
After generation, review the images and ask the agent to revise anything you dislike or replace it with another approved image. When no image changes are needed and the content is complete, ask for `releasekit-finalize` to confirm the release.
|
|
220
|
+
|
|
201
221
|
**Generated graphics default to dark and light.** Paired variants share one scene brief, preserving geometry, feature meaning, and semantic colors while presentation surfaces adapt. Images are shared across locales.
|
|
202
222
|
|
|
203
223
|
Choose `scene.source` in each note's visual brief before planning:
|
|
@@ -217,6 +237,8 @@ visuals:
|
|
|
217
237
|
|
|
218
238
|
`releasekit image plan <version>` reports pending and reusable assets, with separate `generationRequests` and `providedRequests` counts. It reuses current imports and never calls a model API. Single-theme exports contain one real asset and an explicit fallback.
|
|
219
239
|
|
|
240
|
+
To replace or regenerate an image, import the reviewed result with the same release version and note ID and the intended `--theme`. Import automatically switches shared/themed usage and removes the note's unused managed images after saving, including older imports. Files still referenced by visuals and source originals outside the note's managed assets are preserved. Keep existing variant entries until the replacement is imported. Use `--source provided` when replacing generated illustrations with a supplied image, or `--source generated` for the reverse change when the subject permits it; source and selection are saved together. See [image transitions](kit/references/theme-pairing.md#switch-between-shared-and-themed-images).
|
|
241
|
+
|
|
220
242
|
Requests with `action: generate` include a prompt for the agent or an external tool. Requests with `action: provide` have `promptFile: null` and identify the needed source. Missing supplied media stays pending and blocks finalization; the agent can continue independent writing and translation work.
|
|
221
243
|
|
|
222
244
|
<details>
|
|
@@ -233,7 +255,7 @@ releasekit image import 1.4.0 product-detail --theme shared --file ./approved-ca
|
|
|
233
255
|
|
|
234
256
|
The CLI keeps the original bytes, dimensions, and colors. One `shared` asset serves both viewer themes without generating or duplicating another file.
|
|
235
257
|
|
|
236
|
-
If genuine dark/light captures exist, import them with `--theme dark` and `--theme light` instead.
|
|
258
|
+
If genuine dark/light captures exist, import them with `--theme dark` and `--theme light` instead. The CLI replaces the previous shared/themed selection during import; keep its metadata in place until the new file is successfully registered. A missing configured capture remains a supplied-image request.
|
|
237
259
|
|
|
238
260
|
Follow the current image plan even if older prompt files remain. See the [supplied-media example](examples/provided-media/README.md) and [media source guide](kit/references/media-sources.md) for pending inputs and older briefs.
|
|
239
261
|
|
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AssetVariant, type Visual } from './model.js';
|
|
2
|
+
import { Project } from './project.js';
|
|
3
|
+
export declare function fileKey(file: string): Promise<string>;
|
|
4
|
+
export declare function visualFileReferences(project: Project, version: string, file: string, selected?: Visual): Promise<Set<string>>;
|
|
5
|
+
export declare function retainedImageFiles(project: Project, replacement: {
|
|
6
|
+
version: string;
|
|
7
|
+
noteId: string;
|
|
8
|
+
visual: Visual | null;
|
|
9
|
+
}): Promise<Set<string>>;
|
|
10
|
+
export declare function managedAssetFiles(project: Project, version: string, noteId?: string, variant?: AssetVariant): Promise<string[]>;
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import { visualSchema } from './model.js';
|
|
3
|
+
import { Project } from './project.js';
|
|
4
|
+
import { exists, identifier, readYaml, within } from './files.js';
|
|
5
|
+
// References remain readable while a draft's scene brief is unfinished.
|
|
6
|
+
const imageReferencesSchema = visualSchema.pick({ variants: true }).extend({
|
|
7
|
+
scene: visualSchema.shape.scene.pick({ references: true }).strip(),
|
|
8
|
+
}).strip();
|
|
9
|
+
export async function fileKey(file) {
|
|
10
|
+
const resolved = await exists(file) ? await fs.realpath(file) : file;
|
|
11
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
12
|
+
}
|
|
13
|
+
export async function visualFileReferences(project, version, file, selected) {
|
|
14
|
+
const visual = selected ?? await readYaml(file, imageReferencesSchema);
|
|
15
|
+
const references = new Set();
|
|
16
|
+
for (const asset of Object.values(visual.variants)) {
|
|
17
|
+
if (asset)
|
|
18
|
+
references.add(await fileKey(await project.releaseFile(version, asset.file)));
|
|
19
|
+
}
|
|
20
|
+
for (const reference of visual.scene.references)
|
|
21
|
+
references.add(await fileKey(await within(project.root, reference)));
|
|
22
|
+
return references;
|
|
23
|
+
}
|
|
24
|
+
export async function retainedImageFiles(project, replacement) {
|
|
25
|
+
const retained = new Set();
|
|
26
|
+
for (const version of await project.versions()) {
|
|
27
|
+
const directory = await project.releaseFile(version, 'visuals');
|
|
28
|
+
if (!(await exists(directory)))
|
|
29
|
+
continue;
|
|
30
|
+
for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
|
|
31
|
+
if (entry.isDirectory() || !entry.name.endsWith('.yaml'))
|
|
32
|
+
continue;
|
|
33
|
+
const replacing = version === replacement.version && entry.name === `${replacement.noteId}.yaml`;
|
|
34
|
+
if (replacing && replacement.visual === null)
|
|
35
|
+
continue;
|
|
36
|
+
const file = await project.releaseFile(version, `visuals/${entry.name}`);
|
|
37
|
+
for (const reference of await visualFileReferences(project, version, file, replacing ? replacement.visual : undefined)) {
|
|
38
|
+
retained.add(reference);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return retained;
|
|
43
|
+
}
|
|
44
|
+
export async function managedAssetFiles(project, version, noteId, variant) {
|
|
45
|
+
if (noteId !== undefined)
|
|
46
|
+
identifier(noteId);
|
|
47
|
+
const directory = await project.releaseFile(version, 'assets');
|
|
48
|
+
if (!(await exists(directory)))
|
|
49
|
+
return [];
|
|
50
|
+
const prefix = noteId === undefined ? '' : `${noteId}.${variant ? `${variant}.` : ''}`;
|
|
51
|
+
const suffix = noteId === undefined
|
|
52
|
+
? /^[a-zA-Z0-9][a-zA-Z0-9._+-]{0,95}\.(dark|light|shared)\.[a-f0-9]{12}\.(png|jpg|webp)$/
|
|
53
|
+
: variant ? /^[a-f0-9]{12}\.(png|jpg|webp)$/ : /^(dark|light|shared)\.[a-f0-9]{12}\.(png|jpg|webp)$/;
|
|
54
|
+
const files = [];
|
|
55
|
+
for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
|
|
56
|
+
if (entry.isFile() && entry.name.startsWith(prefix) && suffix.test(entry.name.slice(prefix.length))) {
|
|
57
|
+
files.push(await project.releaseFile(version, `assets/${entry.name}`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return files.sort();
|
|
61
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
|
|
|
4
4
|
import { Command, Option } from 'commander';
|
|
5
5
|
import { Project, prepare, startProject } from './project.js';
|
|
6
6
|
import { initProject, installSkills } from './install.js';
|
|
7
|
-
import { addNote, markTranslation, syncImagePolicy } from './content.js';
|
|
7
|
+
import { addNote, removeNote, markTranslation, syncImagePolicy } from './content.js';
|
|
8
8
|
import { planImages, importImage } from './images.js';
|
|
9
9
|
import { validate, finalize } from './validate.js';
|
|
10
10
|
import { exportBundle } from './export.js';
|
|
@@ -50,6 +50,8 @@ note.command('add <version> <id>').description('Scaffold a note and its locale f
|
|
|
50
50
|
await addNote(project(), version, id, noteMetaSchema.shape.category.parse(options.category), options.image);
|
|
51
51
|
emit({ version, note: id, status: 'draft' });
|
|
52
52
|
});
|
|
53
|
+
note.command('remove <version> <id>').description('Remove a draft note, its locale files, prompts, and unused managed images')
|
|
54
|
+
.action(async (version, id) => emit(await removeNote(project(), version, id)));
|
|
53
55
|
const images = program.command('image').description('Plan themed illustrations and register selected files');
|
|
54
56
|
images.command('plan <version>').description('Plan generation or supplied-image requests without calling a model')
|
|
55
57
|
.option('--sync-config', 'apply current project image settings to this draft')
|
|
@@ -59,10 +61,11 @@ images.command('plan <version>').description('Plan generation or supplied-image
|
|
|
59
61
|
await syncImagePolicy(instance, version);
|
|
60
62
|
emit(await planImages(instance, version));
|
|
61
63
|
});
|
|
62
|
-
images.command('import <version> <note>').description('Import
|
|
63
|
-
.addOption(new Option('--theme <theme>', 'variant to register; shared
|
|
64
|
+
images.command('import <version> <note>').description('Import or replace an image, switch shared/themed usage, and remove unused note images')
|
|
65
|
+
.addOption(new Option('--theme <theme>', 'variant to register; shared replaces themed entries and a theme replaces shared').choices(['dark', 'light', 'shared']).makeOptionMandatory())
|
|
66
|
+
.addOption(new Option('--source <source>', 'save the media source with this import; otherwise keep the current source').choices(['generated', 'provided']))
|
|
64
67
|
.requiredOption('--file <file>', 'selected local PNG, JPEG, or WebP')
|
|
65
|
-
.action(async (version, id, options) => emit(await importImage(project(), version, id, assetVariant.parse(options.theme), path.resolve(program.opts().cwd, options.file))));
|
|
68
|
+
.action(async (version, id, options) => emit(await importImage(project(), version, id, assetVariant.parse(options.theme), path.resolve(program.opts().cwd, options.file), { source: options.source })));
|
|
66
69
|
const translation = program.command('translation').description('Track source freshness for reviewed translations');
|
|
67
70
|
translation.command('mark <version> <note>').description('Mark an already reviewed translation current')
|
|
68
71
|
.requiredOption('--locale <locale>', 'translation language code')
|
|
@@ -79,9 +82,9 @@ program.command('validate [version]').description('Validate one release or all r
|
|
|
79
82
|
program.command('finalize <version>').description('Validate and mark local release content ready')
|
|
80
83
|
.action(async (version) => emit(await finalize(project(), version)));
|
|
81
84
|
program.command('export').description('Export recent version groups and selected image variants')
|
|
82
|
-
.
|
|
83
|
-
.option('--limit <count>', 'number of version groups, including current', value => Number(value))
|
|
84
|
-
.option('--locale <locale>', 'output language, defaults to the
|
|
85
|
+
.option('--current <version>', 'current release version, defaults to the release with no successor')
|
|
86
|
+
.option('--limit <count>', 'number of version groups, including current, defaults to project history.limit (initially 3)', value => Number(value))
|
|
87
|
+
.option('--locale <locale>', 'output language, defaults to all locales saved in the current release')
|
|
85
88
|
.requiredOption('--out <directory>', 'new output directory')
|
|
86
89
|
.action(async (options) => emit(await exportBundle(project(), options.current, { ...options, out: path.resolve(program.opts().cwd, options.out) })));
|
|
87
90
|
async function main() {
|
package/dist/content.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { type NoteMeta } from './model.js';
|
|
2
2
|
import { Project } from './project.js';
|
|
3
3
|
export declare function addNote(project: Project, version: string, id: string, category: NoteMeta['category'], image: boolean): Promise<void>;
|
|
4
|
+
export declare function removeNote(project: Project, version: string, id: string): Promise<{
|
|
5
|
+
version: string;
|
|
6
|
+
note: string;
|
|
7
|
+
status: "draft" | "ready";
|
|
8
|
+
removedPaths: string[];
|
|
9
|
+
retainedAssets: string[];
|
|
10
|
+
}>;
|
|
4
11
|
export declare function markTranslation(project: Project, version: string, id: string, language: string): Promise<string>;
|
|
5
12
|
export declare function syncImagePolicy(project: Project, version: string): Promise<void>;
|
|
6
13
|
export declare function readVisual(project: Project, version: string, id: string): Promise<{
|
package/dist/content.js
CHANGED
|
@@ -1,28 +1,119 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileKey, managedAssetFiles, retainedImageFiles, visualFileReferences } from './assets.js';
|
|
2
4
|
import { locale, noteMetaSchema, visualSchema, imageSource } from './model.js';
|
|
3
5
|
import { identifier, writeYaml, writeNote, readNote, noteHash, exists, readYaml } from './files.js';
|
|
4
6
|
import { Project, editable } from './project.js';
|
|
7
|
+
async function notePaths(project, version, id) {
|
|
8
|
+
return Promise.all([
|
|
9
|
+
`notes/${id}`, `visuals/${id}.yaml`,
|
|
10
|
+
...['dark', 'light', 'shared'].map(variant => `prompts/${id}.${variant}.md`),
|
|
11
|
+
].map(relative => project.releaseFile(version, relative)));
|
|
12
|
+
}
|
|
5
13
|
export async function addNote(project, version, id, category, image) {
|
|
6
14
|
const release = await project.release(version);
|
|
7
15
|
editable(release);
|
|
8
16
|
identifier(id);
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
const paths = await notePaths(project, version, id);
|
|
18
|
+
if (release.notes.some(note => note.id === id) || (await Promise.all(paths.map(exists))).some(Boolean)) {
|
|
19
|
+
throw new Error(`Note ${id} already exists or has leftover files; its content was preserved.`);
|
|
11
20
|
}
|
|
12
21
|
const note = noteMetaSchema.parse({ id, category, commits: [], paths: [], image });
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
const directory = paths[0], visualFile = paths[1];
|
|
23
|
+
await fs.mkdir(path.dirname(directory), { recursive: true });
|
|
24
|
+
await fs.mkdir(directory);
|
|
25
|
+
let createdVisual = false;
|
|
26
|
+
try {
|
|
27
|
+
for (const language of release.locales) {
|
|
28
|
+
await writeNote(await project.releaseFile(version, `notes/${id}/${language}.md`), { title: '', alt: '', sourceHash: null, body: '' });
|
|
29
|
+
}
|
|
30
|
+
if (image) {
|
|
31
|
+
await writeYaml(visualFile, {
|
|
32
|
+
schemaVersion: 1,
|
|
33
|
+
scene: { archetype: '', source: '', subject: '', message: '', focus: '', composition: '', context: '', elements: [], preserve: [], avoid: [], text: [], references: [] },
|
|
34
|
+
variants: {},
|
|
35
|
+
});
|
|
36
|
+
createdVisual = true;
|
|
37
|
+
}
|
|
38
|
+
release.notes.push(note);
|
|
39
|
+
release.emptyReason = null;
|
|
40
|
+
release.contentHash = null;
|
|
41
|
+
await project.save(release);
|
|
15
42
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (createdVisual)
|
|
45
|
+
await fs.unlink(visualFile);
|
|
46
|
+
await fs.rm(await project.releaseFile(version, `notes/${id}`), { recursive: true, force: true });
|
|
47
|
+
throw error;
|
|
22
48
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
await project.
|
|
49
|
+
}
|
|
50
|
+
export async function removeNote(project, version, id) {
|
|
51
|
+
const release = await project.release(version);
|
|
52
|
+
editable(release);
|
|
53
|
+
identifier(id);
|
|
54
|
+
if (!release.notes.some(note => note.id === id))
|
|
55
|
+
throw new Error(`Unknown note: ${id}`);
|
|
56
|
+
const paths = await notePaths(project, version, id);
|
|
57
|
+
const directory = await project.releaseDir(version);
|
|
58
|
+
const visualFile = paths[1];
|
|
59
|
+
const references = await exists(visualFile) ? await visualFileReferences(project, version, visualFile) : new Set();
|
|
60
|
+
const retained = await retainedImageFiles(project, { version, noteId: id, visual: null });
|
|
61
|
+
const owned = new Set(await managedAssetFiles(project, version, id));
|
|
62
|
+
const retainedAssets = [];
|
|
63
|
+
for (const file of await managedAssetFiles(project, version)) {
|
|
64
|
+
const key = await fileKey(file);
|
|
65
|
+
if (!owned.has(file) && !references.has(key))
|
|
66
|
+
continue;
|
|
67
|
+
if (retained.has(key))
|
|
68
|
+
retainedAssets.push(file);
|
|
69
|
+
else
|
|
70
|
+
paths.push(file);
|
|
71
|
+
}
|
|
72
|
+
const targets = [];
|
|
73
|
+
for (const file of paths) {
|
|
74
|
+
if (!(await exists(file)))
|
|
75
|
+
continue;
|
|
76
|
+
const key = await fileKey(file);
|
|
77
|
+
if ([...retained].some(reference => reference === key || reference.startsWith(`${key}${path.sep}`))) {
|
|
78
|
+
throw new Error(`Note files are referenced by another visual; update those references before removing ${id}: ${file}`);
|
|
79
|
+
}
|
|
80
|
+
targets.push(file);
|
|
81
|
+
}
|
|
82
|
+
// Hold files inside this release until its metadata is saved, so a failed save can restore them.
|
|
83
|
+
const holding = await fs.mkdtemp(await project.releaseFile(version, `.remove-${id}-`));
|
|
84
|
+
const checkedHolding = await project.releaseFile(version, path.basename(holding));
|
|
85
|
+
const moved = [];
|
|
86
|
+
try {
|
|
87
|
+
for (const [index, original] of targets.entries()) {
|
|
88
|
+
const temporary = path.join(checkedHolding, String(index));
|
|
89
|
+
await fs.rename(original, temporary);
|
|
90
|
+
moved.push({ original, temporary });
|
|
91
|
+
}
|
|
92
|
+
await project.save({ ...release, notes: release.notes.filter(note => note.id !== id), contentHash: null });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const failures = [];
|
|
96
|
+
for (const entry of moved.reverse()) {
|
|
97
|
+
try {
|
|
98
|
+
await fs.rename(entry.temporary, entry.original);
|
|
99
|
+
}
|
|
100
|
+
catch (restoreError) {
|
|
101
|
+
failures.push(restoreError);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (failures.length)
|
|
105
|
+
throw new AggregateError([error, ...failures], `Note removal failed; recovery files remain in ${checkedHolding}`);
|
|
106
|
+
await fs.rmdir(checkedHolding);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await fs.rm(await project.releaseFile(version, path.basename(checkedHolding)), { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
throw new Error(`Note ${id} was removed, but file cleanup is incomplete in ${checkedHolding}`, { cause: error });
|
|
114
|
+
}
|
|
115
|
+
const relative = (file) => path.relative(directory, file).split(path.sep).join('/');
|
|
116
|
+
return { version, note: id, status: release.status, removedPaths: targets.map(relative), retainedAssets: retainedAssets.map(relative) };
|
|
26
117
|
}
|
|
27
118
|
export async function markTranslation(project, version, id, language) {
|
|
28
119
|
const release = await project.release(version);
|
package/dist/export.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Project } from './project.js';
|
|
2
|
-
export declare function exportBundle(project: Project, current: string, options: {
|
|
2
|
+
export declare function exportBundle(project: Project, current: string | undefined, options: {
|
|
3
3
|
limit?: number;
|
|
4
4
|
locale?: string;
|
|
5
5
|
out: string;
|
|
6
6
|
}): Promise<{
|
|
7
|
-
|
|
7
|
+
files: string[];
|
|
8
8
|
releases: number;
|
|
9
9
|
assets: number;
|
|
10
10
|
}>;
|
package/dist/export.js
CHANGED
|
@@ -7,38 +7,47 @@ import { exists, readNote, write } from './files.js';
|
|
|
7
7
|
import { readVisual } from './content.js';
|
|
8
8
|
export async function exportBundle(project, current, options) {
|
|
9
9
|
const config = await project.config();
|
|
10
|
-
const
|
|
11
|
-
const history = await project.history(
|
|
12
|
-
const
|
|
13
|
-
const copies = [];
|
|
10
|
+
const version = current ?? await project.latestVersion();
|
|
11
|
+
const history = await project.history(version, options.limit ?? config.history.limit);
|
|
12
|
+
const languages = options.locale === undefined ? history[0].locales : [locale.parse(options.locale)];
|
|
14
13
|
for (const release of history) {
|
|
15
14
|
if (release.status !== 'ready')
|
|
16
15
|
throw new Error(`Release ${release.version} is still a draft.`);
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
for (const language of languages) {
|
|
17
|
+
if (!release.locales.includes(language))
|
|
18
|
+
throw new Error(`Release ${release.version} has no ${language} locale.`);
|
|
19
|
+
}
|
|
19
20
|
const checked = await validate(project, release.version);
|
|
20
21
|
if (!checked.valid)
|
|
21
22
|
throw new Error(checked.errors.join('\n'));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
}
|
|
24
|
+
const bundles = [];
|
|
25
|
+
const copies = new Map();
|
|
26
|
+
for (const language of languages) {
|
|
27
|
+
const bundle = { schemaVersion: 1, currentVersion: version, locale: language, releases: [] };
|
|
28
|
+
for (const release of history) {
|
|
29
|
+
const entry = { version: release.version, releasedAt: release.releasedAt, previous: release.previous, notes: [] };
|
|
30
|
+
for (const note of release.notes) {
|
|
31
|
+
const text = await readNote(await project.releaseFile(release.version, `notes/${note.id}/${language}.md`));
|
|
32
|
+
const exported = { id: note.id, category: note.category, title: text.title, bodyMarkdown: text.body, image: null };
|
|
33
|
+
if (note.image) {
|
|
34
|
+
const visual = await readVisual(project, release.version, note.id);
|
|
35
|
+
const variants = activeVariants(visual, release.visuals);
|
|
36
|
+
exported.image = { alt: text.alt, fallbackTheme: variants[0], variants: {} };
|
|
37
|
+
for (const variant of variants) {
|
|
38
|
+
const asset = visual.variants[variant];
|
|
39
|
+
const relative = `assets/${release.version}/${path.posix.basename(asset.file)}`;
|
|
40
|
+
exported.image.variants[variant] = { src: relative, width: asset.width, height: asset.height };
|
|
41
|
+
copies.set(relative, await project.releaseFile(release.version, asset.file));
|
|
42
|
+
}
|
|
35
43
|
}
|
|
44
|
+
entry.notes.push(exported);
|
|
36
45
|
}
|
|
37
|
-
|
|
46
|
+
bundle.releases.push(entry);
|
|
38
47
|
}
|
|
39
|
-
|
|
48
|
+
bundleSchema.parse(bundle);
|
|
49
|
+
bundles.push(bundle);
|
|
40
50
|
}
|
|
41
|
-
bundleSchema.parse(bundle);
|
|
42
51
|
const destination = path.resolve(options.out);
|
|
43
52
|
const contentRoot = await project.content('releases');
|
|
44
53
|
const relativeToContent = path.relative(contentRoot, destination);
|
|
@@ -50,9 +59,14 @@ export async function exportBundle(project, current, options) {
|
|
|
50
59
|
}
|
|
51
60
|
if (await exists(destination))
|
|
52
61
|
throw new Error('The export destination already exists. Choose a new output directory.');
|
|
53
|
-
// No filesystem output is created until every selected release has passed validation.
|
|
54
|
-
for (const
|
|
55
|
-
await write(path.join(destination,
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
// No filesystem output is created until every selected release and locale has passed validation.
|
|
63
|
+
for (const [relative, src] of copies)
|
|
64
|
+
await write(path.join(destination, relative), await fs.readFile(src));
|
|
65
|
+
const files = [];
|
|
66
|
+
for (const bundle of bundles) {
|
|
67
|
+
const file = path.join(destination, `release-notes.${bundle.locale}.json`);
|
|
68
|
+
await write(file, JSON.stringify(bundle, null, 2) + '\n');
|
|
69
|
+
files.push(file);
|
|
70
|
+
}
|
|
71
|
+
return { files, releases: history.length, assets: copies.size };
|
|
58
72
|
}
|
package/dist/images.d.ts
CHANGED
|
@@ -31,7 +31,10 @@ export declare function planImages(project: Project, version: string): Promise<{
|
|
|
31
31
|
providedRequests: number;
|
|
32
32
|
costNote: string;
|
|
33
33
|
}>;
|
|
34
|
-
export
|
|
34
|
+
export interface ImportImageOptions {
|
|
35
|
+
source?: 'generated' | 'provided';
|
|
36
|
+
}
|
|
37
|
+
export declare function importImage(project: Project, version: string, noteId: string, variant: AssetVariant, source: string, options?: ImportImageOptions): Promise<{
|
|
35
38
|
file: string;
|
|
36
39
|
sha256: string;
|
|
37
40
|
sceneHash: string;
|
package/dist/images.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import sharp from 'sharp';
|
|
4
|
-
import { themes, assetVariant, imageSource, activeVariants } from './model.js';
|
|
4
|
+
import { themes, assetVariant, sceneSchema, imageSource, activeVariants } from './model.js';
|
|
5
5
|
import { Project, editable } from './project.js';
|
|
6
6
|
import { readVisual, checkReferenceFiles } from './content.js';
|
|
7
7
|
import { digest, identifier, exists, write, writeYaml } from './files.js';
|
|
8
8
|
import { imagePrompt, sceneHash } from './prompts.js';
|
|
9
|
+
import { fileKey, managedAssetFiles, retainedImageFiles } from './assets.js';
|
|
9
10
|
export async function inspectImage(bytes) {
|
|
10
11
|
const image = sharp(bytes, { limitInputPixels: 16_777_216, failOn: 'warning' });
|
|
11
12
|
const metadata = await image.metadata();
|
|
@@ -74,7 +75,19 @@ export async function planImages(project, version) {
|
|
|
74
75
|
costNote: 'Counts describe required output assets, not provider prices or a guarantee of one tool call per asset. No image service was called.',
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
|
-
|
|
78
|
+
async function obsoleteNoteImages(project, version, noteId, selected) {
|
|
79
|
+
const candidates = await managedAssetFiles(project, version, noteId);
|
|
80
|
+
if (!candidates.length)
|
|
81
|
+
return [];
|
|
82
|
+
const retained = await retainedImageFiles(project, { version, noteId, visual: selected });
|
|
83
|
+
const obsolete = [];
|
|
84
|
+
for (const file of candidates) {
|
|
85
|
+
if (!retained.has(await fileKey(file)))
|
|
86
|
+
obsolete.push(file);
|
|
87
|
+
}
|
|
88
|
+
return obsolete;
|
|
89
|
+
}
|
|
90
|
+
export async function importImage(project, version, noteId, variant, source, options = {}) {
|
|
78
91
|
const release = await project.release(version);
|
|
79
92
|
editable(release);
|
|
80
93
|
identifier(noteId);
|
|
@@ -82,34 +95,44 @@ export async function importImage(project, version, noteId, variant, source) {
|
|
|
82
95
|
if (!release.notes.some(n => n.id === noteId && n.image))
|
|
83
96
|
throw new Error(`No image-enabled note named ${noteId}.`);
|
|
84
97
|
const visual = await readVisual(project, version, noteId);
|
|
98
|
+
if (options.source !== undefined)
|
|
99
|
+
visual.scene.source = sceneSchema.shape.source.parse(options.source);
|
|
85
100
|
const provided = imageSource(visual.scene) === 'provided';
|
|
86
101
|
if (variant === 'shared') {
|
|
87
102
|
if (!provided)
|
|
88
|
-
throw new Error('Only supplied images can use a shared asset.');
|
|
89
|
-
|
|
90
|
-
throw new Error('Remove the themed variant entries before switching to one shared supplied image.');
|
|
103
|
+
throw new Error('Only supplied images can use a shared asset. Use --source provided when importing a supplied replacement.');
|
|
104
|
+
visual.variants = {};
|
|
91
105
|
}
|
|
92
106
|
else {
|
|
93
107
|
if (!themes(release.visuals).includes(variant))
|
|
94
108
|
throw new Error(`Theme ${variant} is not enabled for this release. Update the project setting and sync the draft first.`);
|
|
95
|
-
|
|
96
|
-
throw new Error('Remove the shared variant entry before switching to distinct supplied theme variants.');
|
|
109
|
+
delete visual.variants.shared;
|
|
97
110
|
}
|
|
98
111
|
const bytes = await fs.readFile(path.resolve(project.root, source));
|
|
99
112
|
const inspected = await inspectImage(bytes);
|
|
100
113
|
const file = `assets/${noteId}.${variant}.${inspected.sha256.slice(0, 12)}.${inspected.extension}`;
|
|
101
114
|
const destination = await project.releaseFile(version, file);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
else
|
|
107
|
-
await write(destination, bytes);
|
|
115
|
+
const destinationExists = await exists(destination);
|
|
116
|
+
if (destinationExists && digest(await fs.readFile(destination)) !== inspected.sha256)
|
|
117
|
+
throw new Error('The asset destination has conflicting content.');
|
|
108
118
|
visual.variants[variant] = {
|
|
109
119
|
file, sha256: inspected.sha256, sceneHash: sceneHash(visual.scene, release.visuals, variant),
|
|
110
120
|
width: inspected.width, height: inspected.height,
|
|
111
121
|
};
|
|
112
|
-
|
|
122
|
+
// Resolve cleanup before writing, and remove old files only after the selection is saved.
|
|
123
|
+
const obsolete = await obsoleteNoteImages(project, version, noteId, visual);
|
|
124
|
+
if (!destinationExists)
|
|
125
|
+
await write(destination, bytes);
|
|
126
|
+
try {
|
|
127
|
+
await writeYaml(await project.releaseFile(version, `visuals/${noteId}.yaml`), visual);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (!destinationExists)
|
|
131
|
+
await fs.unlink(destination);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
for (const oldFile of obsolete)
|
|
135
|
+
await fs.rm(oldFile, { force: true });
|
|
113
136
|
return visual.variants[variant];
|
|
114
137
|
}
|
|
115
138
|
export async function validateImages(project, version, noteId, visual, errors, warnings) {
|
package/dist/project.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class Project {
|
|
|
10
10
|
release(version: string): Promise<Release>;
|
|
11
11
|
save(release: Release): Promise<void>;
|
|
12
12
|
versions(): Promise<string[]>;
|
|
13
|
+
latestVersion(): Promise<string>;
|
|
13
14
|
history(version: string, limit: number): Promise<Release[]>;
|
|
14
15
|
}
|
|
15
16
|
export declare function editable(release: Release): void;
|
package/dist/project.js
CHANGED
|
@@ -37,6 +37,19 @@ 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 latestVersion() {
|
|
41
|
+
const versions = await this.versions();
|
|
42
|
+
if (!versions.length)
|
|
43
|
+
throw new Error('No releases to export. Prepare and finalize a release first.');
|
|
44
|
+
const releases = await Promise.all(versions.map(version => this.release(version)));
|
|
45
|
+
const predecessors = new Set(releases.map(release => release.previous));
|
|
46
|
+
const latest = releases.filter(release => !predecessors.has(release.version));
|
|
47
|
+
if (!latest.length)
|
|
48
|
+
throw new Error('No latest release found: previous-release links contain a cycle.');
|
|
49
|
+
if (latest.length > 1)
|
|
50
|
+
throw new Error(`Multiple latest releases found: ${latest.map(release => release.version).join(', ')}. Specify --current <version>.`);
|
|
51
|
+
return latest[0].version;
|
|
52
|
+
}
|
|
40
53
|
async history(version, limit) {
|
|
41
54
|
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
42
55
|
throw new Error('History limit must be an integer from 1 to 100.');
|
package/kit/references/format.md
CHANGED
|
@@ -14,6 +14,14 @@ Within one release:
|
|
|
14
14
|
| `prompts/<id>.<theme>.md` | Generation requests for pending generated variants; supplied images have no generation request |
|
|
15
15
|
| `assets/` | Selected raster files with content-derived names |
|
|
16
16
|
|
|
17
|
+
Each `(version, note.id, variant)` has one selected image. Importing replaces the selected slot and then removes unused managed images belonging to this note, including obsolete shared or themed imports. Files referenced by any visual variant or scene in the project are retained, as are other notes' files and source originals outside the note's managed assets. Reimporting identical content reuses its file. Keep existing variant entries until the replacement import succeeds.
|
|
18
|
+
|
|
19
|
+
Importing `--theme shared` replaces the note's dark/light entries with one shared entry. Importing `--theme dark` or `light` replaces a shared entry and keeps compatible themed entries. Optional `--source generated|provided` updates `scene.source` in the same save as the imported selection; omission preserves the current source. Shared imports require supplied media, and supplied-only subjects still reject generated media. The CLI validates the file before saving, so decoding or metadata-save failure preserves the previous source and selections. A missing configured counterpart stays pending after the first themed import and blocks finalization. See [image transitions](theme-pairing.md#switch-between-shared-and-themed-images).
|
|
20
|
+
|
|
21
|
+
`releasekit note remove <version> <id>` is available only for drafts. It removes the ordered metadata entry, `notes/<id>/` with all locale files, `visuals/<id>.yaml`, `prompts/<id>.<variant>.md`, and unused managed raster imports associated with the note, including older variants. Managed imports use `assets/<id>.<variant>.<12-character-hash>.(png|jpg|webp)` names. Files still referenced by any remaining visual are retained; manually named assets and external source originals are preserved. References to the note's text, brief, or prompts must be resolved before removal. The CLI reports removed paths and retained assets. A failed metadata save restores the removed files; any subsequent cleanup failure is reported with the recovery directory rather than as a successful cleanup.
|
|
22
|
+
|
|
23
|
+
Adding a note creates the release's saved locale files and clears `emptyReason`. Notes are image-enabled by default; `--no-image` records an explicit text-only choice. The agent keeps this default for all notes rather than selecting an illustrated subset. Removing the last note does not invent an empty-release reason: the draft needs another note or a factual `emptyReason` before finalization. Ready releases must be reopened before either operation.
|
|
24
|
+
|
|
17
25
|
Preparation writes only `release.yaml`: `source` records the immutable Git boundaries, and each note later records its relevant commits or paths. No full patch or separate changed-file index is stored. Draft validation checks note references against the pinned Git range or the baseline snapshot for a summary; finalization fingerprints the metadata, note text, and visual briefs. Ready content can be validated and exported without Git history.
|
|
18
26
|
|
|
19
27
|
Optional `initialContent` in a baseline release snapshots the selected `summary` or `history` mode. Either mode requires `source.fromRef: null`, `source.fromSha: null`, and `previous: null`. A summary describes the product at `source.toSha`; its evidence may reference only that SHA or tracked paths in that snapshot. History mode analyzes the full history through that SHA with normal range evidence. This distinction is preserved in the content fingerprint but excluded from consumer JSON. An absent field keeps the existing range semantics, including full history when `fromSha` is null; loading old files adds no defaults or changes to their fingerprints.
|
|
@@ -28,6 +36,8 @@ Frontmatter fields are `title`, `alt`, and `sourceHash`. The source locale norma
|
|
|
28
36
|
|
|
29
37
|
The `releasekit-finalize` skill reviews the release and runs `releasekit finalize`. This CLI command checks references and content, then records `status: ready` and a content fingerprint. A later edit invalidates that fingerprint. Reopen the draft before changing content; publishing is a separate user-controlled workflow.
|
|
30
38
|
|
|
31
|
-
The generated JSON schemas shipped with the package are the structural source of truth. `releasekit export
|
|
39
|
+
The generated JSON schemas shipped with the package are the structural source of truth. `releasekit export --out <directory>` produces one `release-notes.<locale>.json` per locale saved in the current release, with shared relative image assets in `assets/`. `--locale <locale>` selects one language and keeps the same filename pattern. Each JSON file uses the existing bundle schema and includes only display fields, configured image variants, its locale, and explicit version groups. Source patches, prompts, internal paths, and Git evidence are not included. Consumers should safely render `bodyMarkdown` and use image `variants[theme]` or `variants[fallbackTheme]` without recoloring the raster.
|
|
40
|
+
|
|
41
|
+
Only `--out` is required. Without `--current`, export selects the unique release that no other saved release names as `previous`, regardless of version spelling, date, or directory order. No releases or cyclic links prevent automatic selection; multiple endpoints require `--current`. A draft endpoint must be finalized before export. Without `--limit`, export uses project `history.limit` (initially 3), following `previous` links. The locales saved in the current release determine the default language set, independently of later project configuration changes. Every selected release must contain each requested locale; missing or stale translations fail the whole export before creating output. The command result contains a `files` array of JSON paths, a `releases` count of version groups, and an `assets` count of image files copied once across locales.
|
|
32
42
|
|
|
33
43
|
An export destination must not already exist. This avoids overwriting content or mixing assets from separate builds. Validation completes before the destination is created.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Choosing the image source
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Choose a truthful source for each note's image before composition and rendering: a supported generated explanation or approved supplied media. When a real capture or approved artwork is required but unavailable, request it and keep the image pending. Follow [image coverage](theme-pairing.md#coverage-and-repeat-runs); omit images only for the user's explicit text-only choices.
|
|
4
4
|
|
|
5
5
|
| Source | Appropriate use | Agent action |
|
|
6
6
|
| --- | --- | --- |
|
|
@@ -27,8 +27,8 @@ A native photo, content image, or screenshot often has one authentic appearance.
|
|
|
27
27
|
releasekit image import 1.4.0 product-detail --theme shared --file ./approved-capture.png
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
This requires `source: provided
|
|
30
|
+
This requires `source: provided`; pass `--source provided` with the import to change the source and selected image together. The CLI copies the selected bytes unchanged. `variants.shared` stores one asset; the public bundle exports one file with `fallbackTheme: shared`. The normal consumer lookup, `variants[theme] ?? variants[fallbackTheme]`, displays that file in either viewer theme. A shared slot is not a fabricated pair and does not require a second generation or duplicate file. Its original dimensions and colors are retained.
|
|
31
31
|
|
|
32
|
-
If the product actually supplies distinct dark/light captures, import those with `--theme dark` and `--theme light`. Once one themed capture is imported, the plan requests the remaining configured capture.
|
|
32
|
+
If the product actually supplies distinct dark/light captures, import those with `--theme dark` and `--theme light`. Once one themed capture is imported, the plan requests the remaining configured capture. The CLI switches between shared and themed selections during import; keep the previous entries until it succeeds. See [image transitions](theme-pairing.md#switch-between-shared-and-themed-images). A missing theme is never generated as a substitute for an authentic capture.
|
|
33
33
|
|
|
34
34
|
Inspect the content and crop before import. Keep the original source while preparing any user-authorized crop or presentation adjustment. Theme changes must not alter product content. File validation checks bytes and metadata; the agent's review establishes whether the selected media is the appropriate approved source.
|
|
@@ -10,6 +10,16 @@ This generation policy does not require inventing a second appearance for suppli
|
|
|
10
10
|
|
|
11
11
|
Policy is captured in each release when it is prepared. Editing the project default affects new releases. To apply the current project policy to an existing draft, run `releasekit image plan <version> --sync-config`. Previously selected files are retained; themes disabled by the new policy are not exported. Ready releases must be reopened before their policy changes.
|
|
12
12
|
|
|
13
|
+
## Coverage and repeat runs
|
|
14
|
+
|
|
15
|
+
The default image scope is every note in the saved release, including smaller fixes and improvements. Review the current `release.yaml` each time so newly added notes are included. A plain `releasekit-image` invocation uses this full scope without asking the user to pick important notes. Honor an explicitly limited request and the user's explicit text-only choices. If earlier agent prioritization disabled a note's image without such a choice, restore `image: true` and create or complete its visual brief in place, preserving its text, translations, and existing assets. Do not recreate the note. Missing supplied media stays pending instead of making the note text-only.
|
|
16
|
+
|
|
17
|
+
Before planning, complete missing or unfinished briefs for the image-enabled notes. Preserve existing scene specifications and the release's captured theme policy when they have not been changed by the user's request. Run `releasekit image plan <version>` from the current saved state, then handle the missing requests across all notes. Respect `action: generate` versus `action: provide` and the configured dark/light or shared variants.
|
|
18
|
+
|
|
19
|
+
On repeat runs, generate or import only missing images and missing required variants. Reuse existing valid imports and their completed reviews; do not regenerate an accepted image merely because the skill was invoked again. This includes a run after the user adds notes: complete those notes' missing images while retaining earlier ones. If everything is already current, generate nothing and provide the image-review and finalization guidance.
|
|
20
|
+
|
|
21
|
+
An existing image reported as stale or invalid is unresolved, even though its file exists. Name the affected note and the reason, preserve the current selection, and offer a targeted correction; a missing-images request alone does not authorize replacing it. An explicit request to edit, regenerate, or replace an image applies to that target even when the plan considers it current; follow [replacement handling](#replace-or-regenerate-an-image). Keep other accepted images intact and check affected theme counterparts when the scene changes. Do not claim complete coverage or recommend finalization while required assets remain unresolved.
|
|
22
|
+
|
|
13
23
|
## One scene, two presentation treatments
|
|
14
24
|
|
|
15
25
|
Both outputs share the same scene brief. Lock subject identity, geometry, object count, positions, scale, crop, camera, UI topology, action state, chart values, and any allowed literal labels. Change presentation surfaces, neutral values, lighting, shadows, and necessary edge separation. Preserve meaningful status colors and natural photographic or material colors.
|
|
@@ -35,7 +45,34 @@ Do not invert pixels or shift brightness globally. A black lens remains a black
|
|
|
35
45
|
5. When the available tool supports image references or edits, use the counterpart for a constrained theme edit. Otherwise repeat the exact scene contract and inspect for layout drift. Never claim pixel-identical geometry from independent stochastic generations.
|
|
36
46
|
6. Compare the pair. Both files should have the same pixel dimensions. Verify pose, crop, UI state, values, and semantic colors by sight, then import the selected counterpart.
|
|
37
47
|
|
|
38
|
-
Use one file per theme, not a split canvas or a two-panel comparison image. Keep
|
|
48
|
+
Use one file per theme, not a split canvas or a two-panel comparison image. Keep the current selection until a reviewed replacement is imported into the same slot. Do not restart the entire release when one small defect can be corrected locally.
|
|
49
|
+
|
|
50
|
+
## Replace or regenerate an image
|
|
51
|
+
|
|
52
|
+
Treat replacement and regeneration as an edit to the existing release, note ID, and affected theme. Reopen a ready release as a draft before editing it. Reuse its scene brief and current assets as needed for the requested correction. A request to regenerate an image still needs work even if the unchanged asset is reported as current by the plan; report the requested replacement as pending until it has been generated, reviewed, and imported.
|
|
53
|
+
|
|
54
|
+
Keep the existing variant metadata while preparing the candidate, then run `releasekit image import <version> <note> --theme <theme> --file <selected-file>` for the same slot. Import validates the candidate and saves the new selection before removing unused managed images for this note, including older imports and obsolete shared/themed files. Reimporting identical bytes reuses the same file. Other selected variants, notes, releases, declared image references, and original source files outside the note's managed assets are preserved. If decoding or saving fails, the previous source and selection remain intact; report the replacement as pending.
|
|
55
|
+
|
|
56
|
+
Keep temporary generation candidates outside the release's `assets/` directory and remove task-created discarded candidates when the replacement is complete. Do not add a new note or clear the old variant entry to make a replacement request. A replacement that remains one shared supplied image stays in the `shared` slot. Use [the transition flow](#switch-between-shared-and-themed-images) when the requested replacement changes between shared and themed usage.
|
|
57
|
+
|
|
58
|
+
### Switch between shared and themed images
|
|
59
|
+
|
|
60
|
+
Keep the current variant entries while preparing and reviewing the replacement. Import with the requested target `--theme`; the CLI replaces incompatible shared or themed entries automatically and cleans unused managed images after saving. When the requested media source also changes, pass `--source provided` or `--source generated` so that source and selection are saved together. Do not pre-clear variants or separately change `scene.source` merely to perform this transition.
|
|
61
|
+
|
|
62
|
+
For example, replace generated dark/light illustrations with one approved capture:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
releasekit image import 1.4.0 queue-action --theme shared --source provided --file ./approved-capture.png
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
To replace that shared image with distinct approved theme captures:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
releasekit image import 1.4.0 queue-action --theme dark --file ./approved-dark.png
|
|
72
|
+
releasekit image import 1.4.0 queue-action --theme light --file ./approved-light.png
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The source remains supplied when `--source` is omitted. For a requested generated explanation instead, use `--source generated` on the first themed import; supplied-only subjects still require real media. Use only the release's configured themes. The first themed import replaces the shared selection, while any required counterpart remains pending until imported. Never synthesize or duplicate a supplied counterpart or describe a partial pair as complete. A source or scene change can also make an existing themed counterpart stale; review and refresh that affected image before finalization.
|
|
39
76
|
|
|
40
77
|
## External generation handoff
|
|
41
78
|
|
|
@@ -70,4 +70,4 @@ Make the brief concrete enough that another model can render the same scene. “
|
|
|
70
70
|
|
|
71
71
|
Inspect the selected image at full resolution and at roughly 350 pixels wide. First compare the image with the release note, product evidence, and scene-specific constraints; use the chosen recipe's correctness checks. Then assess whether the changed capability reads in a moment, the focal object remains distinct, incidental detail stays subordinate, and every explicit label and crop is correct. Attractive styling and theme similarity do not establish factual or structural correctness.
|
|
72
72
|
|
|
73
|
-
For a pair, compare both outputs side by side using [theme-pairing.md](theme-pairing.md). Automated checks establish file integrity, dimensions, configured variants, and scene freshness; they do not prove visual correspondence or truthfulness. Correct a specific defect with a targeted edit instead of randomly regenerating every asset. Preserve accepted
|
|
73
|
+
For a pair, compare both outputs side by side using [theme-pairing.md](theme-pairing.md). Automated checks establish file integrity, dimensions, configured variants, and scene freshness; they do not prove visual correspondence or truthfulness. Correct a specific defect with a targeted edit instead of randomly regenerating every asset. Preserve unrelated accepted assets. Import a reviewed replacement into the same note and theme slot so unused older managed files are removed; keep the previous selection until that import succeeds.
|
|
@@ -4,19 +4,29 @@ Use the installed `releasekit` CLI, or the repository's compiled CLI when develo
|
|
|
4
4
|
|
|
5
5
|
## Create or continue
|
|
6
6
|
|
|
7
|
-
The normal skill flow is `releasekit-draft` (source and selected translations), `releasekit-image` (required assets), then `releasekit-finalize` (review, validation, and local confirmation). Translation-only edits also belong to `releasekit-draft`. Skip image work for a text-only
|
|
7
|
+
The normal skill flow is `releasekit-draft` (source and selected translations), `releasekit-image` (required assets), then `releasekit-finalize` (review, validation, and local confirmation). Translation-only edits also belong to `releasekit-draft`. Skip image work for a release the user explicitly chose to keep text-only; export follows finalization only when requested.
|
|
8
8
|
|
|
9
9
|
1. Read `releasekit/config.yaml` and any existing `release.yaml`. [Choose languages](#choose-languages) with the user before preparing or writing the draft. Honor the theme policy and product context.
|
|
10
10
|
2. For a new release, [resolve the version and Git boundaries from the repository](#resolve-release-scope-from-the-repository). Reuse explicit choices and saved boundaries, inspect the relevant release line, and supply the CLI arguments yourself. Briefly state the selected scope and continue when the evidence is clear. For first-use requests with unresolved earlier-history scope, follow [the adoption guide](adoption.md) to summarize, analyze, or skip the period through the baseline.
|
|
11
11
|
3. Run `releasekit prepare`. It creates only `release.yaml`, including the pinned comparison start and end SHAs. Inspect the pinned Git evidence as described below: baseline summaries read the snapshot; other drafts read history, changed paths, and relevant diffs. Do not save a full patch or a separate changed-file index.
|
|
12
|
-
4. Save the selected `sourceLocale` and `locales` in this release before adding notes with `releasekit note add <version> <id>`. Fill the source Markdown and attach evidence paths or commit SHAs to `release.yaml`, using snapshot evidence for a baseline summary.
|
|
12
|
+
4. Save the selected `sourceLocale` and `locales` in this release before adding notes with `releasekit note add <version> <id>`. Fill the source Markdown and attach evidence paths or commit SHAs to `release.yaml`, using snapshot evidence for a baseline summary. Keep every note image-enabled by default; use `--no-image` only for the user's explicit text-only choice, never to select just the important notes.
|
|
13
13
|
5. As part of `releasekit-draft`, [translate the selected locales](#translate-selected-locales) and mark reviewed translations current. Finish the source and selected translations before recommending image work, unless the user explicitly limited the draft scope.
|
|
14
|
-
6. Use `releasekit-image` to
|
|
14
|
+
6. Use `releasekit-image` to cover all current drafted notes, including later additions, under [the coverage policy](theme-pairing.md#coverage-and-repeat-runs). Choose generated or supplied media and complete each missing visual brief, reusing existing valid images on repeat runs. Run `releasekit image plan`, then handle each request by its action: generate configured variants for `generate`, or find/request an approved capture/image for `provide`. Review and import selected files, using one shared supplied asset when appropriate. Preserve unrelated accepted images and manual edits. For a replacement or regeneration, import into the same note with the intended theme; follow [image transitions](theme-pairing.md#switch-between-shared-and-themed-images) when changing shared/themed usage. Unused managed files are removed after the new selection is saved.
|
|
15
15
|
7. Use `releasekit-finalize` to review factual and visual accuracy, run `releasekit validate <version>`, resolve errors and review warnings, then run `releasekit finalize <version>`. Confirm `status: ready` and a recorded content fingerprint. Review is part of finalization; a validation report alone does not complete this step.
|
|
16
|
-
8. If requested, export
|
|
16
|
+
8. If requested, run `releasekit export --out <directory>` to export recent history to a new output directory. Omit `--current` to use the unique release with no successor in the saved `previous` links; pass an explicitly requested version or resolve multiple release endpoints with `--current <version>`. Omit `--limit` to use project `history.limit` (initially 3). Omit `--locale` to export all locales saved in the current release as separate `release-notes.<locale>.json` files sharing one `assets/` directory; pass it only when the user requests a specific output language. Every selected version must contain those locales. A finalized release is a complete local result even without an export. Finalization does not tag, commit, push, deploy, or publish anything.
|
|
17
17
|
|
|
18
18
|
For an existing draft, read and edit the existing content. `prepare` never overwrites a release. Do not recreate a folder as a shortcut for refreshing one note. Reopen a ready release by setting `status: draft` and `contentHash: null`, then make the targeted change and finalize again.
|
|
19
19
|
|
|
20
|
+
## Revise draft notes
|
|
21
|
+
|
|
22
|
+
Use the user's requested feature or wording to identify the affected notes in the saved release. Keep its pinned Git boundaries and other releases unchanged. The user can ask for copy changes, exclude a feature, or include a feature that the draft missed without invoking a skill manually.
|
|
23
|
+
|
|
24
|
+
- For wording changes, edit the existing source and refresh affected translations. If only part of a note is excluded, revise that note and its visual brief as needed.
|
|
25
|
+
- To include an omitted feature, verify it against the pinned evidence. Extend an existing note when the feature belongs there; otherwise run `releasekit note add <version> <id>` and write its source, selected translations, and evidence. New notes are image-enabled by default; use `--no-image` only when the user explicitly asks for text-only content. Keep required images pending so the next `releasekit-image` run picks up the new note. Explain a requested feature outside the saved scope instead of silently expanding the Git range.
|
|
26
|
+
- To exclude an entire note, run `releasekit note remove <version> <id>`. Let the CLI remove its metadata entry, complete note folder, visual brief, generated prompts, and unused managed images. Removing only the `release.yaml` entry leaves files behind. The command preserves assets referenced by remaining visuals, including other releases, and source originals outside managed assets; report any retained shared assets. If another scene references the note's text, brief, or prompts, resolve that dependency before removal.
|
|
27
|
+
|
|
28
|
+
After additions or removals, validate the release and report outstanding copy, translation, or image work. The CLI clears a previous `emptyReason` when adding a note. Removing the last note leaves an editable empty draft; add another note or write a factual `emptyReason` before finalization. Do not invent a reason to hide an unfinished draft. A failed metadata save restores removed files or rolls back newly added note files.
|
|
29
|
+
|
|
20
30
|
## Resolve release scope from the repository
|
|
21
31
|
|
|
22
32
|
Treat versions, tags, commit SHAs, and CLI arguments as repository discovery work. Missing flags in the user's request are not by themselves a reason to open the question UI. Read the request, saved releases, applicable history-start settings, local tags, and release metadata before deciding that scope is missing.
|
|
@@ -56,9 +66,11 @@ Review each affected translation against the current source, then run `releaseki
|
|
|
56
66
|
|
|
57
67
|
Apply this guidance throughout all three ReleaseKit skills, including questions within a step and choices about what to do next. Read the request, conversation, saved release, and relevant evidence before asking. Reuse established choices and resolve routine editorial or implementation details with judgment. Ask when missing information or a user preference materially affects the result and cannot be resolved from that context. Resolve technical parameters such as Git ranges through [repository inspection](#resolve-release-scope-from-the-repository); the picker is for consequential user choices. Draft language selection follows [Choose languages](#choose-languages).
|
|
58
68
|
|
|
69
|
+
Keep at most one unanswered question request in the conversation across all skills, releases, and question tools. Before every question-tool call, check for a pending request, including one opened before this skill began. If any question in it remains unanswered, do not call `request_user_input`, `request_user_input_async`, or another question tool, even for a different topic. Keep newly discovered questions queued in conversation context until the current request is resolved. Do not replace, refresh, or expand an open picker by issuing another request, and never run question calls in parallel.
|
|
70
|
+
|
|
59
71
|
For a decision needed before proceeding, prefer a native question tool that waits for the answer when exposed and permitted. In Codex, use `request_user_input` only when its mode restrictions and tool instructions allow the question. Otherwise, `request_user_input_async` requires the explicit [answer-waiting procedure](#wait-for-the-users-answer). In another agent, use its available equivalent. If no supported question tool can preserve that wait, ask in chat and yield for a reply. Follow the tool's current schema; do not change modes or install anything solely to display a picker.
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
When no request is pending, bundle related missing decisions into one tool call within its question limit. Queue any remaining decisions for later calls, after this request is resolved. Use the user's language and identify the affected release or notes. When there are meaningful alternatives, offer a few distinct, actionable choices and put the recommended one first, explaining its effect briefly. Keep built-in free-text input available; do not duplicate a built-in Other option or assume multi-select support. For open-ended text such as a path or terminology, use the tool's free-text question when supported instead of inventing arbitrary choices.
|
|
62
74
|
|
|
63
75
|
Use structured questions for text decisions and existing file paths. Request uploads, screenshots, or photographs through the conversation's supported attachment flow, not through a text-only question tool. Reuse suitable approved files already available before requesting new input.
|
|
64
76
|
|
|
@@ -80,13 +92,13 @@ A successful question-tool return may only acknowledge that the question was dis
|
|
|
80
92
|
|
|
81
93
|
After an asynchronous question:
|
|
82
94
|
|
|
83
|
-
1. Continue only authorized work that does not depend on the unanswered choice. Keep dependent preparation, settings writes, copy, image work, finalization, export, or the proposed next step pending as applicable.
|
|
95
|
+
1. Retain the pending request identifier when supplied, its unanswered questions, and queued questions in conversation context. Continue only authorized work that does not depend on the unanswered choice; discovering another missing decision queues it without opening a new picker or asking a competing question in chat. Keep dependent preparation, settings writes, copy, image work, finalization, export, or the proposed next step pending as applicable.
|
|
84
96
|
2. When no independent work remains, use an input-aware wait tool, such as `clock.sleep` when exposed, in calls of at most 60 seconds. Check for a submitted reply after each wait and keep waiting if none arrived. An elapsed wait is not permission to choose a default. Do not simulate waiting with shell sleeps, dummy commands, or unrelated tool calls.
|
|
85
|
-
3. Keep the question open while waiting. Do not send a final response just to say that you are waiting or announce completion while its answer is pending; ending the turn can clear the unanswered picker. A skill transition
|
|
97
|
+
3. Keep the question open while waiting. Do not send a final response just to say that you are waiting or announce completion while its answer is pending; ending the turn can clear the unanswered picker. A skill transition or resumed context must retain the pending request and queue; neither allows a new picker or dependent work to proceed. A short progress update belongs in commentary and must not claim a selection.
|
|
86
98
|
|
|
87
|
-
Check that an input-aware wait is available before opening a nonblocking picker. If it is unavailable, ask the question in chat and yield for the user's reply. If an opened picker
|
|
99
|
+
Check that an input-aware wait is available before opening a nonblocking picker. If it is unavailable, ask the question in chat and yield for the user's reply. If the environment confirms an opened picker was cleared without an answer or cannot remain open, preserve its unresolved questions, state that no answer was received, and ask only those questions once in chat instead. Keep that fallback pending before asking queued questions. Lack of a reply or visibility alone does not establish that a picker closed; continue waiting when its state is uncertain. Do not duplicate a still-open usable picker or treat a cleanup notification or fallback as an answer.
|
|
88
100
|
|
|
89
|
-
After a submitted answer, apply that choice and resume the work it unblocks.
|
|
101
|
+
After a submitted answer, apply that choice and resume the work it unblocks. A partial reply leaves the other questions in that request pending. Only after every question in the current request is answered or explicitly stopped, deferred, or delegated to the agent may another request be opened. Recheck queued questions against the new answers and completed work, discard those already resolved or no longer needed, and ask only the remaining necessary questions. Reuse resolved choices across skill transitions. An explicit stop or deferral pauses the affected work without selecting an option. Continue already requested work that needs no new choice without introducing another confirmation.
|
|
90
102
|
|
|
91
103
|
## Continue to the next step
|
|
92
104
|
|
|
@@ -98,17 +110,30 @@ Choose the next action from the actual state, with priority for useful work that
|
|
|
98
110
|
| --- | --- |
|
|
99
111
|
| Source copy or evidence is incomplete, or the user wants revisions | Continue `releasekit-draft` for the affected notes, including selected translations. |
|
|
100
112
|
| Selected translations are missing or stale | Continue `releasekit-draft` for the affected languages and notes using [Translate selected locales](#translate-selected-locales). |
|
|
101
|
-
| Source and translations are complete; required briefs or
|
|
113
|
+
| Source and translations are complete; required briefs or images are missing | Recommend `releasekit-image` to fill missing images across all current notes under [the coverage policy](theme-pairing.md#coverage-and-repeat-runs). Reuse valid images from previous runs. |
|
|
114
|
+
| Existing images are stale or invalid | Identify the affected notes and offer a targeted image correction or replacement. Preserve their selections until replacements succeed. |
|
|
102
115
|
| A supplied image or generation tool is unavailable | Name the exact missing input and keep assets pending. Offer unfinished draft or translation work only when it can usefully proceed. |
|
|
103
116
|
| Copy, translations, and required images are complete; the release is a draft | Recommend `releasekit-finalize` to review, resolve validation findings, and mark the release ready in one step. Reuse completed review when content is unchanged. |
|
|
104
117
|
| The release is ready and a requested export remains | Continue with the selected locale, version window, and a new output directory; collect only missing export choices. |
|
|
105
118
|
| The release is ready and no export was requested, or the requested export is delivered | Deliver the result links and finish. |
|
|
106
119
|
|
|
107
|
-
Resolve validation failures before finalization or export. Skip image work for text-only
|
|
120
|
+
Resolve validation failures before finalization or export. Skip image work for notes the user explicitly chose to keep text-only and translation work for a single-language release or current translations. An intentionally empty release with a factual `emptyReason` can proceed directly to `releasekit-finalize`. Do not recommend completed work again merely to follow a fixed sequence.
|
|
121
|
+
|
|
122
|
+
Continue steps already included in the user's request in the same conversation, using the corresponding installed skill or its shared references and CLI. Announce the next action without asking for another confirmation. If the release still needs work beyond the completed request, first check for any pending question request. Queue the next-step choice while one remains pending; a completed stage does not allow another picker. Otherwise, use the [native question UI](#ask-with-the-native-question-ui) to offer the recommended next action first, one useful alternative when available, and a choice to finish the current stage. At the end of a completed drafting step, use [Finish drafting](#complete-drafting-and-invite-revisions); for other stages or incomplete drafting, use Stop for now. Keep choices concise and describe the work in ordinary language so the user does not need to know a skill name or CLI command. Preserve free-text input for another direction, then follow [the answer-waiting procedure](#wait-for-the-users-answer). Keep the picker open until the user answers; an unanswered or preselected option does not start additional work.
|
|
123
|
+
|
|
124
|
+
Carry out the selected step without making the user invoke another skill manually. Reuse the release version, pinned range, language choices, and accepted assets. After that step completes or encounters a blocker, return to this state check and recommend the next useful action. Honor an explicit request to stop, pause, or do only the current step without follow-up questions. Do not open any new question request while an earlier request is still pending, even if it concerns another step or release. Once the release is finalized and any requested export is delivered, finish with the result links; publishing is not an automatic next stage.
|
|
125
|
+
|
|
126
|
+
### Complete drafting and invite revisions
|
|
127
|
+
|
|
128
|
+
Once the requested source-copy and translation work is complete, label the stage-completion choice **Finish drafting** (in Korean, **초안 작성 완료**). Its description should explain that the draft is saved and the user can ask the agent for revisions after reading it. Avoid labels such as "finish in draft status" or "초안 상태로 마무리", which suggest permanently ending the release work. Keep incomplete copy or translations visible; do not offer this completion choice while requested drafting work remains.
|
|
129
|
+
|
|
130
|
+
When the user selects this choice, or the requested drafting-only work is complete, report what was drafted, link the written notes and translations, and state any deferred translations, pending images, or finalization work. Invite the user to request changes in the same conversation, for example, "Rewrite this feature description," "Leave this feature out of the release notes," or "Add the feature that was missed." Explain that the agent will revise the saved draft and refresh affected translations. The user does not need to edit files or invoke a skill manually. Keep the release in draft status and finish this response without opening another next-step picker or starting image work or finalization. Resume the saved release when the user requests revisions or a later stage.
|
|
131
|
+
|
|
132
|
+
### Review images and continue
|
|
108
133
|
|
|
109
|
-
|
|
134
|
+
After completing image work, show previews or useful links to the accepted images and report coverage against the release's current notes and theme policy. Invite the user to ask the agent to revise an image or replace it with another approved image, for example, "Simplify the queue feature image" or "Replace this feature image with this screenshot." The agent handles the existing note and affected image slots; the user does not need to edit files or invoke another skill manually.
|
|
110
135
|
|
|
111
|
-
|
|
136
|
+
When required images, copy, and translations are complete, explain that the user can ask for `releasekit-finalize` to review and confirm this release if no image changes are needed. When offering next steps, use **Finalize release** as the recommended action and **Request image changes or replacement** as the useful alternative, retaining the option to stop for now. Continue finalization without another confirmation when it was already requested. For an image-only request, deliver the images with this guidance and keep the release as a draft. If a repeat run finds no missing images, reuse the same guidance without generating new ones. Name any missing, stale, or invalid assets and their next action before offering completion; unresolved images still block finalization.
|
|
112
137
|
|
|
113
138
|
## Inspect the pinned changes
|
|
114
139
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Writing product release notes
|
|
2
2
|
|
|
3
|
-
Write for the person using the product, using its actual terminology and the configured language.
|
|
3
|
+
Write for the person using the product, using its actual terminology and the configured language. Use a short feature name as the title and a short paragraph about what changed. Add a second paragraph for the action path, requirements, or a material limitation when supported by evidence. More detail is appropriate for a genuinely complex change; concision is not a reason to remove necessary operating instructions.
|
|
4
4
|
|
|
5
5
|
Group commits into user-visible changes. Let the final diff and target revision establish what shipped. A merged commit can have been reverted; a feature can have been renamed; internal maintenance can have no useful user-facing announcement. Do not translate each commit subject into a separate card.
|
|
6
6
|
|
|
@@ -10,22 +10,49 @@ Keep source evidence with each note. Do not invent performance percentages, priv
|
|
|
10
10
|
|
|
11
11
|
Avoid hype, congratulations, “we are excited,” vague “various enhancements,” engineering implementation details with no user consequence, and repeated starts that make every note sound the same. Use active statements about the product's behavior. A small fix can be one precise sentence.
|
|
12
12
|
|
|
13
|
+
## Newly supported capabilities
|
|
14
|
+
|
|
15
|
+
When the pinned before-and-after evidence shows that an action was unsupported before this release and is supported at the target revision, prefer “이제 ~할 수 있습니다.” in Korean or a natural equivalent such as “You can now …” in English. State the newly possible action and its conditions. If support was added only for a format, platform, or mode, name that scope instead of implying the whole feature is new. A `feature` category or the current implementation alone does not establish prior lack of support.
|
|
16
|
+
|
|
17
|
+
Use this construction selectively where the transition matters most. Read the notes together in release order for each locale, and avoid repeating “이제,” “~할 수 있습니다,” or “You can now” in consecutive openings or throughout the release. When several notes qualify, prioritize this opening where it best highlights a newly possible action; vary other openings with direct statements such as “~을 지원합니다” or “~ 기능을 추가했습니다,” preserving the meaning and scope. Merely replacing “이제” with “새롭게” or “드디어” does not resolve a repetitive sentence pattern. Use editorial judgment rather than a fixed count or quota.
|
|
18
|
+
|
|
19
|
+
Describe improvements to existing support, performance changes, and bug fixes directly without implying first-time support. If earlier support is uncertain, describe only the verified behavior without claiming it is newly available. The first-use introduction evidence rule above still applies. These fictional examples assume the change in the left column is established:
|
|
20
|
+
|
|
21
|
+
| Established change | Suitable Korean body wording |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| Batch renaming was unsupported and is now supported | 이제 여러 파일의 이름을 한 번에 변경할 수 있습니다. |
|
|
24
|
+
| SVG export was added, and a nearby note already uses the same opening | SVG 형식 내보내기를 지원합니다. |
|
|
25
|
+
| Existing batch renaming became faster | 여러 파일의 이름을 변경할 때 처리 속도를 개선했습니다. |
|
|
26
|
+
| An existing save action could crash | 저장 중 앱이 종료되던 문제를 수정했습니다. |
|
|
27
|
+
|
|
28
|
+
## Titles
|
|
29
|
+
|
|
30
|
+
Name the feature, setting, or product area with a compact noun phrase, preferably using the product's established label. Keep only the qualifiers needed to identify the change or distinguish it from another note. Put usage instructions, benefits, requirements, and longer explanations in the body.
|
|
31
|
+
|
|
32
|
+
Avoid turning titles into instructions, full sentences, or a restatement of the opening paragraph. In Korean, prefer feature names over sentence-like `~하기` constructions; in English, prefer names over imperative or how-to phrases. Use the shortest natural wording that preserves the feature's identity. Established operation names such as `되돌리기` are valid; do not mechanically strip endings or enforce a fixed character limit.
|
|
33
|
+
|
|
34
|
+
| Longer instructional title | Preferred feature title |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Save your changes automatically while you work | Autosave |
|
|
37
|
+
| 원하는 시간에 알림을 받도록 예약하기 | 알림 예약 |
|
|
38
|
+
| 여러 파일의 이름을 한 번에 변경하기 | 일괄 이름 변경 |
|
|
39
|
+
|
|
13
40
|
## Examples from a fictional product
|
|
14
41
|
|
|
15
|
-
Title:
|
|
42
|
+
Title: Queue
|
|
16
43
|
|
|
17
44
|
You can now add a saved item to the queue by swiping its row to the right. The item keeps its existing position in your saved list.
|
|
18
45
|
|
|
19
|
-
Title:
|
|
46
|
+
Title: 대기열
|
|
20
47
|
|
|
21
48
|
이제 저장한 항목을 오른쪽으로 스와이프해 대기열에 추가할 수 있습니다. 저장 목록에서 항목의 위치는 그대로 유지됩니다.
|
|
22
49
|
|
|
23
|
-
Only use
|
|
50
|
+
Use the opening only when evidence establishes that this action was previously unsupported and became available in this release; vary it when nearby notes use the same pattern. Only use the second sentence if the behavior is established by the product evidence. An example is not permission to add a similar claim to another product.
|
|
24
51
|
|
|
25
52
|
## Translation
|
|
26
53
|
|
|
27
54
|
Selected translations are part of `releasekit-draft`. Use that skill for translation-only additions or refreshes as well; follow [the translation workflow](workflow.md#translate-selected-locales) for language scope and source fingerprints.
|
|
28
55
|
|
|
29
|
-
Use the same note ID in every configured locale. Translate user meaning, not word order. Keep product names supplied by the user, supported menu paths, requirements, and numbers consistent. Raster illustrations are shared; localize their alt text separately. Alt text describes the feature-bearing visual rather than the style or color palette.
|
|
56
|
+
Use the same note ID in every configured locale. Translate user meaning, not word order. Preserve whether an action is newly supported or an existing capability changed, and apply the new-capability and repetition guidance across each locale rather than copying every opening mechanically. Keep translated titles as concise feature names, preserving the source subject and necessary qualifiers without expanding them into usage instructions. Keep product names supplied by the user, supported menu paths, requirements, and numbers consistent. Raster illustrations are shared; localize their alt text separately. Alt text describes the feature-bearing visual rather than the style or color palette.
|
|
30
57
|
|
|
31
58
|
After reviewing a translation against the current source, use `releasekit translation mark <version> <note> --locale <locale>`. This records a source fingerprint; it does not prove translation quality. If the original title, alt text, or body changes, review and refresh affected translations before marking them current again.
|
|
@@ -3,14 +3,20 @@ name: releasekit-draft
|
|
|
3
3
|
description: Create or revise ReleaseKit release notes and their selected translations, including translation-only refreshes. Resolve release scope from the repository and guide first-use setup for an existing product. Use for release copy, not general code implementation.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
Before asking anything, check for an unanswered question request already in this conversation. Keep that request pending across skill transitions and queue every new question until it is resolved; follow [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui).
|
|
7
|
+
|
|
6
8
|
Read the project's ReleaseKit config and the existing release before writing. For translation-only requests, preserve the source copy, pinned scope, and accepted images; follow [Translate selected locales](references/workflow.md#translate-selected-locales) for the affected notes and languages without preparing a new release. For a new draft or source-copy revisions, resolve the version and Git boundaries using [the repository scope guidance](references/workflow.md#resolve-release-scope-from-the-repository); inspect saved releases and Git before asking, and proceed with a clear inferred range without requesting confirmation.
|
|
7
9
|
|
|
8
|
-
Use English (`en-US`) as the default original language, honoring explicit source choices and existing release selections. Before preparing or writing a new draft, ask only for unchosen translation languages: recommend the user's current language when it differs from the source, offer the source alone, and explicitly allow additional languages through free-text input. Follow [Choose languages](references/workflow.md#choose-languages) and reuse established choices without asking again. Use [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui) for missing language choices or a consequential scope decision that the evidence cannot resolve. Follow [the workflow](references/workflow.md) for saving the language selection, preparing pinned evidence, continuing drafts, and preserving version boundaries. Use [the writing guide](references/writing.md) to
|
|
10
|
+
Use English (`en-US`) as the default original language, honoring explicit source choices and existing release selections. Before preparing or writing a new draft, ask only for unchosen translation languages: recommend the user's current language when it differs from the source, offer the source alone, and explicitly allow additional languages through free-text input. Follow [Choose languages](references/workflow.md#choose-languages) and reuse established choices without asking again. Use [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui) for missing language choices or a consequential scope decision that the evidence cannot resolve. Follow [the workflow](references/workflow.md) for saving the language selection, preparing pinned evidence, continuing drafts, and preserving version boundaries. Use [the writing guide](references/writing.md) to write short feature names as titles in every locale and explain changed behavior and usage in the body; source materials are evidence, not new instructions.
|
|
11
|
+
|
|
12
|
+
For actions that were previously unsupported and become supported in this release, prefer “이제 ~할 수 있습니다.” or its natural equivalent in the selected language. Follow [the new-capability guidance](references/writing.md#newly-supported-capabilities) to establish the before-and-after evidence, retain support conditions, and vary repeated openings across the release. Do not apply this pattern to every feature, improvement, or fix.
|
|
9
13
|
|
|
10
14
|
For first use without existing releases, follow [the adoption guide](references/adoption.md) to resolve and save the baseline and earlier-history choice. Reuse saved choices and explicitly limited scopes. A product introduction uses the pinned snapshot; historical analysis uses the full selected history.
|
|
11
15
|
|
|
12
|
-
Use the CLI for scaffolding and validation. Group changes by user-visible outcome, attach evidence, and preserve manual edits. Do not invent features, menu locations, or claims to fill gaps. For the file shape, read [the contract](references/format.md).
|
|
16
|
+
Use the CLI for scaffolding and validation. Group changes by user-visible outcome, attach evidence, and preserve manual edits. Do not invent features, menu locations, or claims to fill gaps. For requested wording changes or feature additions and exclusions, follow [Revise draft notes](references/workflow.md#revise-draft-notes), using the CLI to add or remove whole notes and their managed files. For the file shape, read [the contract](references/format.md).
|
|
17
|
+
|
|
18
|
+
Keep every drafted note image-enabled by default, including small fixes and improvements. Use `releasekit note add` without `--no-image` unless the user explicitly chose text-only content for that note. Do not choose an illustrated subset by importance or disable an image because its input is missing. Keep required media pending for `releasekit-image`; follow [image coverage](references/theme-pairing.md#coverage-and-repeat-runs).
|
|
13
19
|
|
|
14
20
|
Drafting includes the source and every selected translation unless the user explicitly limits the language scope. Follow [Translate selected locales](references/workflow.md#translate-selected-locales) to write or refresh title, body, and alt text, then record the source fingerprints only after reviewing each translation. Preserve translations that are already current. A locale file scaffold alone is not a completed translation.
|
|
15
21
|
|
|
16
|
-
After drafting, follow [the next-step workflow](references/workflow.md#continue-to-the-next-step). With source and translations complete, recommend `releasekit-image` when required images remain, or `releasekit-finalize` when the release is ready for final checks. Continue work already requested in this conversation; otherwise offer the next useful action. Do not send completed translations through another stage.
|
|
22
|
+
After drafting, follow [the next-step workflow](references/workflow.md#continue-to-the-next-step). With source and translations complete, recommend `releasekit-image` to cover all drafted notes when required images remain, or `releasekit-finalize` when the release is ready for final checks. Continue work already requested in this conversation; otherwise offer the next useful action. When the user chooses to finish drafting or requested drafting only, [hand over the completed draft and invite revision requests](references/workflow.md#complete-drafting-and-invite-revisions). Do not send completed translations through another stage.
|
|
@@ -3,11 +3,15 @@ name: releasekit-finalize
|
|
|
3
3
|
description: Finalize a ReleaseKit release by reviewing facts, copy, translations, and images, validating content, and marking the local release ready. Export a release bundle when requested.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
Before asking anything, check for an unanswered question request already in this conversation. Keep that request pending across skill transitions and queue every new question until it is resolved; follow [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui).
|
|
7
|
+
|
|
6
8
|
Read the target release, [the workflow](references/workflow.md), and [the content contract](references/format.md). Reuse the version and choices established in the request and conversation. Use [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui) only for unresolved scope or requested export choices.
|
|
7
9
|
|
|
8
10
|
Invoking this skill to finalize a release includes review, corrections within the requested scope, validation, and local finalization. Complete those actions without a separate confirmation step. If the user explicitly asks only for an assessment, report the findings and leave the release status unchanged.
|
|
9
11
|
|
|
10
|
-
Compare claims against the pinned final diff and target-revision files. For `initialContent: summary`, use the baseline snapshot and [the adoption guide](references/adoption.md); do not require a reconstruction of old commits or assume an initial launch. Use [the writing guide](references/writing.md) to check changed behavior, action paths, requirements, and limitations. Refresh affected translations after source edits using [Translate selected locales](references/workflow.md#translate-selected-locales). Preserve current translations and manual edits.
|
|
12
|
+
Compare claims against the pinned final diff and target-revision files. For `initialContent: summary`, use the baseline snapshot and [the adoption guide](references/adoption.md); do not require a reconstruction of old commits or assume an initial launch. Use [the writing guide](references/writing.md) to check concise feature titles in every locale, changed behavior, action paths, requirements, and limitations. Refresh affected translations after source edits using [Translate selected locales](references/workflow.md#translate-selected-locales). Preserve current translations and manual edits.
|
|
13
|
+
|
|
14
|
+
Apply [the new-capability guidance](references/writing.md#newly-supported-capabilities) to prefer “이제 ~할 수 있습니다.” or a natural locale equivalent where evidence establishes newly supported actions. Remove unsupported first-time claims from improvements and fixes, and read the notes together in release order for each locale to vary repetitive openings while preserving the change and its conditions.
|
|
11
15
|
|
|
12
16
|
Inspect selected images for correct subject, readable framing, absent invented details, and consistent geometry across configured themes using [the pairing guide](references/theme-pairing.md). Reuse a completed visual review when the note, brief, and assets are unchanged. The CLI verifies files and metadata; it cannot judge whether the image depicts the feature accurately. Keep missing or unsuitable assets pending and use `releasekit-image` for the needed correction.
|
|
13
17
|
|
|
@@ -15,6 +19,6 @@ Run `releasekit validate <version>`, resolve errors, and assess warnings. Missin
|
|
|
15
19
|
|
|
16
20
|
For a draft that passes review and validation, run `releasekit finalize <version>` and verify that `release.yaml` contains `status: ready` and a nonempty `contentHash`. The command validates again and records the fingerprint; do not set ready status manually. For an already ready release, validate and reuse it without calling finalize again. If requested corrections require changes, reopen it with `status: draft` and `contentHash: null`, apply the corrections, and finalize again.
|
|
17
21
|
|
|
18
|
-
If export was requested, run `releasekit export --
|
|
22
|
+
If export was requested, run `releasekit export --out <directory>`, using [the workflow](references/workflow.md) for defaults. Add `--current`, `--limit`, or `--locale` only for requested overrides or to resolve multiple release endpoints. With no language override, export all locales saved in the current release to separate JSON files that share image assets. Preserve individual release boundaries and configured fallback themes. A successful finalization is a complete local result even when no export was requested. Finalization does not commit, tag, push, deploy, or publish.
|
|
19
23
|
|
|
20
24
|
When finalized, report the version and link to its release file, adding bundle links only for a completed requested export. If blocked, state the remaining work and the actual saved status. Follow [the next-step workflow](references/workflow.md#continue-to-the-next-step) without introducing a separate review stage.
|
|
@@ -3,15 +3,19 @@ name: releasekit-image
|
|
|
3
3
|
description: Create, revise, or import ReleaseKit release illustrations with consistent composition and project-configured dark and light variants. Use for visual release-note assets and generation prompts, not general UI implementation or arbitrary image work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Before asking anything, check for an unanswered question request already in this conversation. Keep that request pending across skill transitions and queue every new question until it is resolved; follow [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui).
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Read the saved release's complete current note list and captured visual policy on every invocation. Default to covering every drafted note, including notes added since earlier image work; honor only the user's explicit text-only choices or explicitly limited request. Follow [coverage and repeat runs](references/theme-pairing.md#coverage-and-repeat-runs) to reuse accepted images and fill missing ones. First choose the [media source](references/media-sources.md), then read [the visual language](references/visual-language.md) and applicable [composition recipe](references/composition-recipes.md). Use [the pairing guide](references/theme-pairing.md) for configured themes, cost-aware reuse, and importing images. Consult [the file contract](references/format.md) when editing a brief.
|
|
9
|
+
|
|
10
|
+
When a user decision is needed during image work, such as an ambiguous target for a requested revision or a meaningful choice among suitable approved references, use [the shared question guidance](references/workflow.md#ask-with-the-native-question-ui). Reuse the captured theme policy and existing asset choices. Ask for missing image attachments through the conversation's supported attachment flow; a text-only picker can collect an existing file path.
|
|
9
11
|
|
|
10
12
|
Derive one visual message from the release note and its Git/product evidence. Choose an archetype and `source`; the scaffold leaves both unselected. Generate flat explanatory graphics when an abstraction is sufficient. `object-detail` and `editorial-scene` require supplied media, and any other type can use an actual capture when fidelity matters. Search existing approved assets or use the user's capture; if absent, ask for the specific image and keep it pending. Do not invent a physical product, content artwork, or decorative 3D scene. Examples illustrate individual features, not default layouts.
|
|
11
13
|
|
|
12
|
-
Complete one shared scene brief
|
|
14
|
+
Complete each missing or unfinished scene brief before planning the release. Preserve the briefs of unchanged accepted images. Each note has one shared scene brief for its configured variants. Encode product facts and uncertainties in `context`, the relevant state and relationships in `composition`, and the feature-specific correctness constraints in `preserve` and `avoid`. Keep reference identities and attributed style names out of prompts and assets. Inspect product references as evidence. Do not invent a concrete UI or physical design to fill missing evidence; use a supported abstraction or leave the unresolved detail in the brief.
|
|
15
|
+
|
|
16
|
+
Run `releasekit image plan <version>` on every invocation. For a normal first or repeat run, fill missing assets under [the coverage policy](references/theme-pairing.md#coverage-and-repeat-runs); a repeat invocation alone does not request regeneration of existing images. Handle requests by `action`: `generate` has a generation prompt; `provide` needs an existing or user-supplied image and has no generation prompt. Respect the latest plan even if older prompt files remain in the release. For generated variants, review and import the first before planning its counterpart. For supplied media, use `--theme shared` for one unchanged source or import distinct genuine theme captures. Keep native appearance and meaning; do not invert, duplicate, or synthesize a missing supplied counterpart.
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
For an image replacement or regeneration request, reuse the existing release and note ID. Keep the current variant metadata until the replacement is reviewed, then run `releasekit image import` with the intended `--theme`. The CLI replaces the selection, switches between shared and themed usage when needed, and removes unused managed images for that note. If the media source changes, include `--source provided` or `--source generated` to save it together with the new selection. Do not pre-clear variants or separately change the source just to switch modes. Do not create another note, release, or permanent candidate asset to represent a revision. Keep failed or unavailable replacements pending; preserve unrelated accepted assets and supplied originals. Follow [replacement handling](references/theme-pairing.md#replace-or-regenerate-an-image), including [shared/themed transitions](references/theme-pairing.md#switch-between-shared-and-themed-images).
|
|
15
19
|
|
|
16
20
|
If no image generator is available, preserve the generated prompt files and report the pending assets. Continue independent editorial work. Do not silently call a paid API, install a provider, or replace requested raster illustrations with placeholders. Import user-supplied PNG, JPEG, or WebP files when they become available.
|
|
17
21
|
|
|
@@ -19,4 +23,4 @@ Inspect selected images at full resolution and small-card size. First compare th
|
|
|
19
23
|
|
|
20
24
|
If the accepted image requires an alt-text correction, update the source and affected translations using [Translate selected locales](references/workflow.md#translate-selected-locales), reviewing them before recording new source fingerprints.
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
After image work, follow [Review images and continue](references/workflow.md#review-images-and-continue): show the accepted images, invite the user to ask the agent for changes or a replacement, and explain that `releasekit-finalize` confirms the release when no image changes are needed and the content is complete. Reuse this handoff when a repeat run finds no missing images. Follow [the next-step workflow](references/workflow.md#continue-to-the-next-step) to continue already requested finalization or offer it as the recommended action. If assets remain missing, stale, or otherwise invalid, identify the affected notes and keep that work pending.
|