@laitszkin/apollo-toolkit 3.3.0 → 3.3.2

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/AGENTS.md CHANGED
@@ -22,7 +22,7 @@ This repository enables users to install and run a curated set of reusable agent
22
22
  - Users can research the latest completed market week and produce a PDF watchlist of tradeable instruments for the coming week.
23
23
  - Users can turn a marked weekly finance PDF into a concise evidence-based financial event report.
24
24
  - Users can install Apollo Toolkit through npm or npx and interactively choose one or more target skill directories to populate with copied or symlinked skills, with the option to include codex-exclusive skills in non-codex targets.
25
- - Users can uninstall all Apollo Toolkit-installed skills from all targets or specific targets via `apltk uninstall`.
25
+ - Users can uninstall Apollo Toolkit-installed skills through an interactive target selector or specific non-interactive targets via `apltk uninstall`.
26
26
  - Users can choose between symlink mode (auto-update via git pull) and copy mode (stable snapshot) with `--symlink` / `--copy` flags.
27
27
  - Users can run bundled helper tools through `apltk tools` and direct `apltk <tool>` commands for selected packaged skill scripts.
28
28
  - Users can design and implement new features through a spec-first workflow.
@@ -79,7 +79,8 @@ This repository enables users to install and run a curated set of reusable agent
79
79
  - `./scripts/install_skills.sh all --copy` - 以複製模式安裝到所有支援目標。
80
80
  - `./scripts/install_skills.sh uninstall` - 從所有目標移除已安裝的技能。
81
81
  - `./scripts/install_skills.sh uninstall codex` - 只從 codex 目標移除。
82
- - `node bin/apollo-toolkit.js uninstall` - 透過 CLI 移除所有已安裝技能。
82
+ - `node bin/apollo-toolkit.js uninstall` - 透過 CLI 互動選擇要移除的 agent target 技能。
83
+ - `node bin/apollo-toolkit.js uninstall codex --yes` - 以非互動方式移除指定 target 的已安裝技能。
83
84
 
84
85
  ## Core Project Purpose
85
86
 
package/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ All notable changes to this repository are documented in this file.
7
7
  ### Added
8
8
  - (None yet)
9
9
 
10
+ ## [v3.3.2] - 2026-04-27
11
+
12
+ ### Changed
13
+ - Tighten `version-release` so GitHub release prerelease state must come from explicit user intent or a verified repository convention, instead of being inferred from tag text such as `alpha-*`.
14
+
15
+ ## [v3.3.1] - 2026-04-26
16
+
17
+ ### Added
18
+ - Add an interactive `apltk uninstall` target selector so users can choose which agent skill directories to remove.
19
+ - Add `apltk uninstall --yes` for non-interactive uninstall confirmation.
20
+
21
+ ### Fixed
22
+ - Fix default `apltk uninstall` cleanup so a missing OpenClaw workspace no longer prevents uninstalling Codex, Trae, Agents, or Claude Code targets.
23
+ - Remove manifest-tracked historical skills during CLI uninstall so renamed or removed skills do not remain behind.
24
+ - Ignore unsafe manifest skill names during install and uninstall cleanup so removals remain scoped to direct child skill directories.
25
+
10
26
  ## [v3.3.0] - 2026-04-26
11
27
 
12
28
  ### Added
package/README.md CHANGED
@@ -82,10 +82,14 @@ The interactive installer:
82
82
  ### Uninstall
83
83
 
84
84
  ```bash
85
- apltk uninstall # Remove all installed skills from all targets
86
- apltk uninstall codex # Remove only from codex
85
+ apltk uninstall # Choose which agent targets to uninstall
86
+ apltk uninstall codex # Remove only from codex
87
+ apltk uninstall codex agents --yes # Non-interactive cleanup for selected targets
87
88
  ```
88
89
 
90
+ The uninstall flow removes the manifest-tracked current and historical skill
91
+ directories for the selected targets, then removes each target manifest.
92
+
89
93
  ### Global install
90
94
 
91
95
  ```bash
package/lib/cli.js CHANGED
@@ -13,12 +13,13 @@ const {
13
13
  syncToolkitHome,
14
14
  uninstallSkills,
15
15
  getTargetRoots,
16
+ getUninstallTargetRoots,
16
17
  } = require('./installer');
17
18
  const { formatToolList, getToolCommand, runTool } = require('./tool-runner');
18
19
  const { checkForPackageUpdate } = require('./updater');
19
20
 
20
21
  const TARGET_OPTIONS = [
21
- { id: 'all', label: 'All', description: 'Install every supported target below' },
22
+ { id: 'all', label: 'All', description: 'Select every supported target below' },
22
23
  ...TARGET_DEFINITIONS,
23
24
  ];
24
25
 
@@ -137,7 +138,7 @@ function buildHelpText({ version, colorEnabled }) {
137
138
  'Usage:',
138
139
  ` apltk [install] [${buildModeUsagePattern()}]...`,
139
140
  ` apollo-toolkit [install] [${buildModeUsagePattern()}]...`,
140
- ' apltk uninstall',
141
+ ` apltk uninstall [${buildModeUsagePattern()}]... [--yes]`,
141
142
  ' apltk tools',
142
143
  ' apltk <tool> [...args]',
143
144
  ' apltk tools <tool> [...args]',
@@ -148,6 +149,7 @@ function buildHelpText({ version, colorEnabled }) {
148
149
  ' apltk',
149
150
  ' apltk codex openclaw',
150
151
  ' apltk uninstall',
152
+ ' apltk uninstall codex agents --yes',
151
153
  ' npx @laitszkin/apollo-toolkit',
152
154
  ' npx @laitszkin/apollo-toolkit codex openclaw',
153
155
  ' npm i -g @laitszkin/apollo-toolkit',
@@ -164,6 +166,7 @@ function buildHelpText({ version, colorEnabled }) {
164
166
  '',
165
167
  'Options:',
166
168
  ' --home <path> Override Apollo Toolkit home directory',
169
+ ' --yes, -y Skip uninstall confirmation',
167
170
  ' --help Show this help text',
168
171
  ].join('\n');
169
172
  }
@@ -200,16 +203,24 @@ function parseArguments(argv) {
200
203
  toolName: null,
201
204
  toolArgs: [],
202
205
  linkMode: null, // 'copy' | 'symlink' | null (prompt)
206
+ assumeYes: false,
203
207
  };
204
208
 
205
209
  if (args[0] === 'uninstall') {
206
210
  result.command = 'uninstall';
207
211
  args.shift();
208
- // remaining args could be specific modes
209
212
  while (args.length > 0) {
210
213
  const arg = args.shift();
211
214
  if (arg === '--help' || arg === '-h') {
212
215
  result.showHelp = true;
216
+ } else if (arg === '--yes' || arg === '-y') {
217
+ result.assumeYes = true;
218
+ } else if (arg === '--home') {
219
+ const toolkitHome = args.shift();
220
+ if (!toolkitHome) {
221
+ throw new Error('Missing value for --home');
222
+ }
223
+ result.toolkitHome = path.resolve(toolkitHome);
213
224
  } else {
214
225
  result.modes.push(arg);
215
226
  }
@@ -281,13 +292,21 @@ function clearScreen(output) {
281
292
  }
282
293
  }
283
294
 
284
- function renderSelectionScreen({ output, version, cursor, selected, message, env }) {
295
+ function renderSelectionScreen({
296
+ output,
297
+ version,
298
+ cursor,
299
+ selected,
300
+ message,
301
+ env,
302
+ intro = 'Choose where Apollo Toolkit should copy managed skills.',
303
+ }) {
285
304
  const colorEnabled = supportsColor(output, env);
286
305
  const allSelected = VALID_MODES.every((mode) => selected.has(mode));
287
306
 
288
307
  clearScreen(output);
289
308
  output.write(`${buildBanner({ version, colorEnabled })}\n\n`);
290
- output.write('Choose where Apollo Toolkit should copy managed skills.\n');
309
+ output.write(`${intro}\n`);
291
310
  output.write(`${color('Use Up/Down', '1;33', colorEnabled)} (or ${color('j/k', '1;33', colorEnabled)}) to move, ${color('Space', '1;33', colorEnabled)} to toggle, ${color('Enter', '1;33', colorEnabled)} to continue.\n`);
292
311
  output.write(`Press ${color('a', '1;33', colorEnabled)} to toggle all, ${color('q', '1;33', colorEnabled)} to cancel.\n\n`);
293
312
 
@@ -308,13 +327,11 @@ function renderSelectionScreen({ output, version, cursor, selected, message, env
308
327
  }
309
328
  }
310
329
 
311
- async function promptForModes({ stdin, stdout, version, env }) {
330
+ async function promptForSelectableModes({ stdin, stdout, version, env, intro, ttyError, cancelMessage }) {
312
331
  if (!stdin.isTTY || !stdout.isTTY) {
313
- throw new Error(`Interactive install requires a TTY. Re-run with targets like ${buildInteractiveModeHint()}.`);
332
+ throw new Error(ttyError);
314
333
  }
315
334
 
316
- await animateWelcomeScreen({ output: stdout, version, env });
317
-
318
335
  return new Promise((resolve, reject) => {
319
336
  let cursor = 0;
320
337
  let message = '';
@@ -348,48 +365,48 @@ async function promptForModes({ stdin, stdout, version, env }) {
348
365
  const value = chunk.toString('utf8');
349
366
  if (value === '') {
350
367
  cleanup();
351
- reject(new Error('Installation cancelled.'));
368
+ reject(new Error(cancelMessage));
352
369
  return;
353
370
  }
354
371
 
355
372
  if (value === '' || value === 'k') {
356
373
  cursor = (cursor - 1 + TARGET_OPTIONS.length) % TARGET_OPTIONS.length;
357
374
  message = '';
358
- renderSelectionScreen({ output: stdout, version, cursor, selected, message, env });
375
+ renderSelectionScreen({ output: stdout, version, cursor, selected, message, env, intro });
359
376
  return;
360
377
  }
361
378
 
362
379
  if (value === '' || value === 'j') {
363
380
  cursor = (cursor + 1) % TARGET_OPTIONS.length;
364
381
  message = '';
365
- renderSelectionScreen({ output: stdout, version, cursor, selected, message, env });
382
+ renderSelectionScreen({ output: stdout, version, cursor, selected, message, env, intro });
366
383
  return;
367
384
  }
368
385
 
369
386
  if (value === ' ') {
370
387
  toggleMode(TARGET_OPTIONS[cursor].id);
371
388
  message = '';
372
- renderSelectionScreen({ output: stdout, version, cursor, selected, message, env });
389
+ renderSelectionScreen({ output: stdout, version, cursor, selected, message, env, intro });
373
390
  return;
374
391
  }
375
392
 
376
393
  if (value.toLowerCase() === 'a') {
377
394
  toggleMode('all');
378
395
  message = '';
379
- renderSelectionScreen({ output: stdout, version, cursor, selected, message, env });
396
+ renderSelectionScreen({ output: stdout, version, cursor, selected, message, env, intro });
380
397
  return;
381
398
  }
382
399
 
383
400
  if (value.toLowerCase() === 'q' || value === '') {
384
401
  cleanup();
385
- reject(new Error('Installation cancelled.'));
402
+ reject(new Error(cancelMessage));
386
403
  return;
387
404
  }
388
405
 
389
406
  if (value === '\r') {
390
407
  if (selected.size === 0) {
391
408
  message = 'Select at least one target before continuing.';
392
- renderSelectionScreen({ output: stdout, version, cursor, selected, message, env });
409
+ renderSelectionScreen({ output: stdout, version, cursor, selected, message, env, intro });
393
410
  return;
394
411
  }
395
412
 
@@ -401,7 +418,32 @@ async function promptForModes({ stdin, stdout, version, env }) {
401
418
  stdin.setRawMode(true);
402
419
  stdin.resume();
403
420
  stdin.on('data', onData);
404
- renderSelectionScreen({ output: stdout, version, cursor, selected, message, env });
421
+ renderSelectionScreen({ output: stdout, version, cursor, selected, message, env, intro });
422
+ });
423
+ }
424
+
425
+ async function promptForModes({ stdin, stdout, version, env }) {
426
+ await animateWelcomeScreen({ output: stdout, version, env });
427
+ return promptForSelectableModes({
428
+ stdin,
429
+ stdout,
430
+ version,
431
+ env,
432
+ intro: 'Choose where Apollo Toolkit should copy managed skills.',
433
+ ttyError: `Interactive install requires a TTY. Re-run with targets like ${buildInteractiveModeHint()}.`,
434
+ cancelMessage: 'Installation cancelled.',
435
+ });
436
+ }
437
+
438
+ async function promptForUninstallModes({ stdin, stdout, version, env }) {
439
+ return promptForSelectableModes({
440
+ stdin,
441
+ stdout,
442
+ version,
443
+ env,
444
+ intro: 'Choose which agent skill targets Apollo Toolkit should uninstall.',
445
+ ttyError: `Interactive uninstall requires a TTY. Re-run with targets like ${buildInteractiveModeHint()}.`,
446
+ cancelMessage: 'Uninstall cancelled.',
405
447
  });
406
448
  }
407
449
 
@@ -525,7 +567,7 @@ function printUninstallSummary({ stdout, uninstallResult, env }) {
525
567
  stdout.write('\n\n');
526
568
  for (const result of uninstallResult) {
527
569
  stdout.write(`${color(result.target, '1', colorEnabled)} (${result.root})\n`);
528
- stdout.write(` Removed: ${result.removedSkills.join(', ')}\n`);
570
+ stdout.write(` Removed: ${result.removedSkills.length > 0 ? result.removedSkills.join(', ') : '(manifest only)'}\n`);
529
571
  }
530
572
  }
531
573
 
@@ -563,17 +605,29 @@ async function run(argv, context = {}) {
563
605
  // --- Uninstall flow ---
564
606
  if (parsed.command === 'uninstall') {
565
607
  const toolkitHome = parsed.toolkitHome || resolveToolkitHome(env);
566
- const modes = parsed.modes.length > 0 ? normalizeModes(parsed.modes) : null;
608
+ const modes = parsed.modes.length > 0
609
+ ? normalizeModes(parsed.modes)
610
+ : (stdin.isTTY && stdout.isTTY
611
+ ? normalizeModes(await promptForUninstallModes({ stdin, stdout, version: packageJson.version, env }))
612
+ : null);
613
+ const modesForLookup = modes || VALID_MODES;
614
+ const targets = await getUninstallTargetRoots(modesForLookup, env);
567
615
 
568
616
  // Show what will be removed
569
- const allKnown = await listAllKnownSkillNames({ toolkitHome, modes: modes || [], env });
617
+ const allKnown = await listAllKnownSkillNames({ toolkitHome, modes: modesForLookup, env });
570
618
  stdout.write(color(`Apollo Toolkit home: ${toolkitHome}\n`, '2', supportsColor(stdout, env)));
619
+ if (targets.length > 0) {
620
+ stdout.write('Targets:\n');
621
+ for (const target of targets) {
622
+ stdout.write(`- ${target.label}: ${target.root}\n`);
623
+ }
624
+ }
571
625
 
572
- const confirmed = await promptYesNo({
626
+ const confirmed = parsed.assumeYes || await promptYesNo({
573
627
  stdin,
574
628
  stdout,
575
629
  env,
576
- question: `This will remove all Apollo Toolkit-installed skills${modes ? ` from: ${modes.join(', ')}` : ' from all targets'}. Continue?`,
630
+ question: `This will remove Apollo Toolkit-installed skills${modes ? ` from: ${modes.join(', ')}` : ' from all targets'}. Continue?`,
577
631
  defaultYes: false,
578
632
  });
579
633
 
@@ -688,6 +742,7 @@ module.exports = {
688
742
  buildToolsHelp,
689
743
  parseArguments,
690
744
  promptForModes,
745
+ promptForUninstallModes,
691
746
  promptSymlinkChoice,
692
747
  promptIncludeExclusiveSkills,
693
748
  readPackageJson,
package/lib/installer.js CHANGED
@@ -122,6 +122,25 @@ async function readManifest(targetRoot) {
122
122
  }
123
123
  }
124
124
 
125
+ function isSafeSkillName(skillName) {
126
+ return typeof skillName === 'string'
127
+ && skillName.length > 0
128
+ && !skillName.includes('\0')
129
+ && !skillName.includes('/')
130
+ && !skillName.includes('\\')
131
+ && !path.isAbsolute(skillName)
132
+ && !path.win32.isAbsolute(skillName)
133
+ && skillName !== '.'
134
+ && skillName !== '..';
135
+ }
136
+
137
+ function getManifestSkillNames(manifest) {
138
+ return [...new Set([
139
+ ...(Array.isArray(manifest.historicalSkills) ? manifest.historicalSkills : []),
140
+ ...(Array.isArray(manifest.skills) ? manifest.skills : []),
141
+ ])].filter(isSafeSkillName).sort();
142
+ }
143
+
125
144
  // Write manifest to a target directory.
126
145
  async function writeManifest(targetRoot, { version, linkMode, skills, previousSkills = [] }) {
127
146
  const historicalSkills = [...new Set([...previousSkills, ...skills])].sort();
@@ -145,17 +164,17 @@ async function listAllKnownSkillNames({ toolkitHome, modes = [], env = process.e
145
164
  const allNames = new Set();
146
165
 
147
166
  // Current skills from toolkit home
148
- const currentSkills = await listSkillNames(toolkitHome, modes);
167
+ const currentSkills = await listSkillNames(toolkitHome, modes).catch(() => []);
149
168
  for (const name of currentSkills) {
150
169
  allNames.add(name);
151
170
  }
152
171
 
153
172
  // Historical skills from all target manifests
154
- const targets = await getTargetRoots(modes, env).catch(() => []);
173
+ const targets = await getUninstallTargetRoots(modes, env);
155
174
  for (const target of targets) {
156
175
  const manifest = await readManifest(target.root);
157
176
  if (manifest && manifest.historicalSkills) {
158
- for (const name of manifest.historicalSkills) {
177
+ for (const name of getManifestSkillNames(manifest)) {
159
178
  allNames.add(name);
160
179
  }
161
180
  }
@@ -343,6 +362,21 @@ async function getTargetRoots(modes, env = process.env) {
343
362
  return targets;
344
363
  }
345
364
 
365
+ async function getUninstallTargetRoots(modes = VALID_MODES, env = process.env) {
366
+ const targets = [];
367
+
368
+ for (const mode of normalizeModes(modes)) {
369
+ try {
370
+ targets.push(...await getTargetRoots([mode], env));
371
+ } catch {
372
+ // Uninstall is best-effort across agents. A missing OpenClaw workspace
373
+ // must not prevent cleanup from Codex, Trae, Agents, or Claude Code.
374
+ }
375
+ }
376
+
377
+ return targets;
378
+ }
379
+
346
380
  async function ensureDirectory(dirPath) {
347
381
  await fsp.mkdir(dirPath, { recursive: true });
348
382
  }
@@ -387,8 +421,8 @@ async function installLinks({ toolkitHome, modes, env = process.env, previousSki
387
421
  // Read existing manifest to carry forward historical skills
388
422
  const existingManifest = await readManifest(target.root);
389
423
  const allPreviousSkills = existingManifest
390
- ? [...new Set([...existingManifest.historicalSkills, ...previousSkillNames])]
391
- : previousSkillNames;
424
+ ? [...new Set([...getManifestSkillNames(existingManifest), ...previousSkillNames.filter(isSafeSkillName)])]
425
+ : previousSkillNames.filter(isSafeSkillName);
392
426
 
393
427
  const staleSkillNames = allPreviousSkills.filter(
394
428
  (skillName) => !targetSkillNames.includes(skillName),
@@ -435,17 +469,18 @@ async function installLinks({ toolkitHome, modes, env = process.env, previousSki
435
469
  // Uninstall all skills from all target directories that have manifests.
436
470
  async function uninstallSkills({ env = process.env, modes = null } = {}) {
437
471
  const normalizedModes = modes ? normalizeModes(modes) : VALID_MODES;
438
- const targets = await getTargetRoots(normalizedModes, env).catch(() => []);
472
+ const targets = await getUninstallTargetRoots(normalizedModes, env);
439
473
  const results = [];
440
474
 
441
475
  for (const target of targets) {
442
476
  const manifest = await readManifest(target.root);
443
- if (!manifest || !manifest.skills || manifest.skills.length === 0) {
477
+ if (!manifest) {
444
478
  continue;
445
479
  }
446
480
 
481
+ const skillNames = getManifestSkillNames(manifest);
447
482
  const removedSkills = [];
448
- for (const skillName of manifest.skills) {
483
+ for (const skillName of skillNames) {
449
484
  const skillPath = path.join(target.root, skillName);
450
485
  try {
451
486
  await fsp.rm(skillPath, { recursive: true, force: true });
@@ -462,13 +497,11 @@ async function uninstallSkills({ env = process.env, modes = null } = {}) {
462
497
  // ok if already gone
463
498
  }
464
499
 
465
- if (removedSkills.length > 0) {
466
- results.push({
467
- target: target.label,
468
- root: target.root,
469
- removedSkills,
470
- });
471
- }
500
+ results.push({
501
+ target: target.label,
502
+ root: target.root,
503
+ removedSkills,
504
+ });
472
505
  }
473
506
 
474
507
  return results;
@@ -480,6 +513,7 @@ module.exports = {
480
513
  VALID_MODES,
481
514
  MANIFEST_FILENAME,
482
515
  getTargetRoots,
516
+ getUninstallTargetRoots,
483
517
  installLinks,
484
518
  listAllKnownSkillNames,
485
519
  listCodexSkillNames,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laitszkin/apollo-toolkit",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "description": "Apollo Toolkit npm installer for managed skill copying across Codex, OpenClaw, and Trae.",
5
5
  "license": "MIT",
6
6
  "author": "LaiTszKin",
@@ -15,7 +15,7 @@ description: "Guide the agent to prepare and publish a versioned release (versio
15
15
  ## Standards
16
16
 
17
17
  - Evidence: Inspect the active change set, current version files, existing tag format, existing remote tags/releases, and root `CHANGELOG.md` `Unreleased` content before touching version files, tags, or release metadata.
18
- - Execution: Use this workflow only for explicit release intent, run the required quality gates when applicable, and treat every conditional gate whose scenario is met as blocking before versioning or publication; hand the repository to `submission-readiness-check` before versioning work, invoke `archive-specs` directly whenever completed plan sets should be converted or project docs need alignment, and if the worktree is already clean inspect the current version, local/remote tag state, and existing GitHub release state before deciding whether the request is already satisfied; when the user explicitly wants the same prerelease version to point at newer fixes, retarget the existing prerelease tag and GitHub release instead of inventing an extra version bump; when editing an existing GitHub prerelease during that retarget flow, use a GitHub-accepted release target such as the intended branch name if the tool or API rejects a raw commit SHA for `target_commitish`; otherwise cut the release directly from `CHANGELOG.md` `Unreleased`, update versions and docs, commit, tag, push, and publish the GitHub release with actual release tooling rather than PR-surrogate directives; run git mutations sequentially and verify both the branch tip and release tag exist remotely before publishing the GitHub release, and never treat UI git directives such as `::git-stage`, `::git-commit`, or `::git-push` as evidence that the release commit or tag already exists.
18
+ - Execution: Use this workflow only for explicit release intent, run the required quality gates when applicable, and treat every conditional gate whose scenario is met as blocking before versioning or publication; hand the repository to `submission-readiness-check` before versioning work, invoke `archive-specs` directly whenever completed plan sets should be converted or project docs need alignment, and if the worktree is already clean inspect the current version, local/remote tag state, and existing GitHub release state before deciding whether the request is already satisfied; decide whether the GitHub release is a prerelease only from explicit user instruction or explicit repository convention already documented in the request context, never from tag naming alone; when the user explicitly wants the same prerelease version to point at newer fixes, retarget the existing prerelease tag and GitHub release instead of inventing an extra version bump; when editing an existing GitHub prerelease during that retarget flow, use a GitHub-accepted release target such as the intended branch name if the tool or API rejects a raw commit SHA for `target_commitish`; otherwise cut the release directly from `CHANGELOG.md` `Unreleased`, update versions and docs, commit, tag, push, and publish the GitHub release with actual release tooling rather than PR-surrogate directives; run git mutations sequentially and verify both the branch tip and release tag exist remotely before publishing the GitHub release, and never treat UI git directives such as `::git-stage`, `::git-commit`, or `::git-push` as evidence that the release commit or tag already exists.
19
19
  - Quality: Never guess versions, align user-facing docs with actual code, do not bypass readiness blockers from `submission-readiness-check`, do not reconstruct release notes from `git diff` when curated changelog content already exists, and do not report release success until the commit, tag, and GitHub release all exist for the same version.
20
20
  - Output: Produce a versioned release commit and tag, publish a matching GitHub release, and keep changelog plus relevant repository documentation synchronized.
21
21
 
@@ -69,6 +69,7 @@ Load only when needed:
69
69
  - Read existing version files (for example `project.toml`, `package.json`, or repo-specific version files).
70
70
  - Infer existing tag format (`vX.Y.Z` or `X.Y.Z`) from repository tags.
71
71
  - Inspect existing local and remote tags plus any existing GitHub Release for the target version before creating new release metadata, so duplicate or conflicting releases are caught early.
72
+ - Determine release channel explicitly before publishing: if the user did not say `prerelease`, `draft`, `latest`, or an equivalent repository-specific release state, default to asking or to the repository's already-documented convention instead of inferring from the tag text.
72
73
  - If the user explicitly asks to keep the same prerelease version or to `repoint`, `retarget`, or `move` an existing prerelease after follow-up fixes, treat that as a retarget flow: keep the version unchanged, confirm the existing prerelease tag/release name, and plan to move that tag/release to the new commit instead of bumping semver.
73
74
  - If the requested version tag and matching published GitHub release already exist and point at the intended commit, report that the release is already complete instead of creating duplicate metadata.
74
75
  - If the user provides the target version, use it directly.
@@ -101,6 +102,7 @@ Load only when needed:
101
102
  - If any git step finishes ambiguously or the remote hashes do not match local state, rerun the missing step sequentially and re-check before publishing the GitHub release.
102
103
  10. Publish the GitHub release
103
104
  - Create a non-draft GitHub release that matches the pushed version tag.
105
+ - Set the GitHub release's prerelease flag only when the user explicitly asked for a prerelease or an already-verified repository convention for this exact release channel requires it.
104
106
  - For prerelease retarget flows, update the existing GitHub prerelease so it points at the rewritten tag/commit and refresh its notes when the new fix changes the shipped behavior.
105
107
  - If the release tool rejects a raw commit SHA while updating `target_commitish`, retry with the authoritative branch name or another GitHub-accepted target identifier, then verify the published release now resolves to the rewritten tag.
106
108
  - Use the release notes from the new `CHANGELOG.md` entry unless the repository has a stronger established release-note source.