release-skill 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +23 -0
  7. package/CONTRIBUTING.md +1 -1
  8. package/INSTALL.md +47 -2
  9. package/INSTALL.zh-CN.md +29 -2
  10. package/README.md +126 -9
  11. package/README.zh-CN.md +108 -9
  12. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  13. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  14. package/adapters/claude/bin/release-skill.bundle.mjs +6408 -1654
  15. package/adapters/claude/schemas/.render-manifest.json +10 -6
  16. package/adapters/claude/schemas/postpublish-approval-record.schema.json +47 -0
  17. package/adapters/claude/schemas/release-plan.schema.json +65 -1
  18. package/adapters/claude/schemas/release-project.schema.json +73 -1
  19. package/adapters/claude/schemas/release-run.schema.json +11 -6
  20. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  21. package/adapters/codex/bin/release-skill.bundle.mjs +6408 -1654
  22. package/adapters/codex/schemas/.render-manifest.json +10 -6
  23. package/adapters/codex/schemas/postpublish-approval-record.schema.json +47 -0
  24. package/adapters/codex/schemas/release-plan.schema.json +65 -1
  25. package/adapters/codex/schemas/release-project.schema.json +73 -1
  26. package/adapters/codex/schemas/release-run.schema.json +11 -6
  27. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  28. package/adapters/kimi/bin/release-skill.bundle.mjs +6408 -1654
  29. package/adapters/kimi/schemas/.render-manifest.json +10 -6
  30. package/adapters/kimi/schemas/postpublish-approval-record.schema.json +47 -0
  31. package/adapters/kimi/schemas/release-plan.schema.json +65 -1
  32. package/adapters/kimi/schemas/release-project.schema.json +73 -1
  33. package/adapters/kimi/schemas/release-run.schema.json +11 -6
  34. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  35. package/adapters/workbuddy/bin/release-skill.bundle.mjs +6408 -1654
  36. package/adapters/workbuddy/schemas/.render-manifest.json +10 -6
  37. package/adapters/workbuddy/schemas/postpublish-approval-record.schema.json +47 -0
  38. package/adapters/workbuddy/schemas/release-plan.schema.json +65 -1
  39. package/adapters/workbuddy/schemas/release-project.schema.json +73 -1
  40. package/adapters/workbuddy/schemas/release-run.schema.json +11 -6
  41. package/bin/release-skill-cli.mjs +181 -3
  42. package/bin/release-skill.bundle.mjs +6408 -1654
  43. package/package.json +2 -1
  44. package/platform-manifest.json +4 -4
  45. package/references/.render-manifest.json +5 -5
  46. package/references/01-state-machine.md +22 -2
  47. package/schemas/.render-manifest.json +10 -6
  48. package/schemas/postpublish-approval-record.schema.json +47 -0
  49. package/schemas/release-plan.schema.json +65 -1
  50. package/schemas/release-project.schema.json +73 -1
  51. package/schemas/release-run.schema.json +11 -6
  52. package/src/commands/approve.mjs +167 -1
  53. package/src/commands/distribute.mjs +411 -33
  54. package/src/commands/postverify.mjs +734 -0
  55. package/src/commands/prepare.mjs +280 -42
  56. package/src/commands/setup.mjs +715 -0
  57. package/src/commands/ship.mjs +152 -5
  58. package/src/commands/verify.mjs +92 -15
  59. package/src/core/approval.mjs +93 -68
  60. package/src/core/bounded-output.mjs +46 -0
  61. package/src/core/derived-artifact-gates.mjs +258 -0
  62. package/src/core/docs-refresh-preset.mjs +167 -0
  63. package/src/core/errors.mjs +4 -0
  64. package/src/core/hooks.mjs +28 -0
  65. package/src/core/marketplace-registry-entry.mjs +174 -0
  66. package/src/core/notify-handoff.mjs +76 -0
  67. package/src/core/postpublish-approval.mjs +110 -0
  68. package/src/core/postpublish.mjs +424 -7
  69. package/src/core/preset-executor.mjs +156 -0
  70. package/src/core/preset-gitwrite.mjs +463 -0
  71. package/src/core/presets.mjs +706 -0
  72. package/src/core/proposal-inbox.mjs +630 -0
  73. package/src/core/run.mjs +91 -6
package/src/core/run.mjs CHANGED
@@ -12,7 +12,7 @@
12
12
  * @module core/run
13
13
  */
14
14
 
15
- import { lstat, mkdir, readFile } from 'node:fs/promises';
15
+ import { lstat, mkdir, readFile, stat } from 'node:fs/promises';
16
16
  import { realpathSync } from 'node:fs';
17
17
  import { dirname, join, resolve, basename, relative, isAbsolute } from 'node:path';
18
18
  import Ajv from 'ajv';
@@ -120,22 +120,107 @@ export function validateRun(run, options = {}) {
120
120
  }
121
121
  }
122
122
 
123
+ /**
124
+ * Resolve a run path that may point at a run DIRECTORY (O6, 2026-08-18
125
+ * release-cycle investigation §3.2): verify `--run` ergonomically accepts a
126
+ * run directory and binds its `release-run.json`; a regular file input is
127
+ * returned unchanged so every existing file-path caller keeps its exact
128
+ * behaviour (compat).
129
+ *
130
+ * Fail-closed rules for the directory form:
131
+ * - a symlinked directory input is rejected (never silently dereferenced);
132
+ * - a directory without `release-run.json` names the expected file.
133
+ * A missing path is returned unchanged so the legacy readFile error surfaces.
134
+ *
135
+ * @param {string} runPath - Run file or run directory path.
136
+ * @returns {Promise<string>} The resolved run FILE path.
137
+ * @throws {ReleaseError} GATE_FAILED for symlinked or release-run.json-less
138
+ * directory inputs.
139
+ */
140
+ export async function resolveRunPath(runPath) {
141
+ let stats;
142
+ try {
143
+ stats = await lstat(runPath);
144
+ } catch (err) {
145
+ if (err?.code === 'ENOENT') return runPath; // legacy readFile error path
146
+ throw new ReleaseError(
147
+ GATE_FAILED,
148
+ `cannot inspect release run path: ${err.message}`,
149
+ { runPath, cause: err.code },
150
+ );
151
+ }
152
+
153
+ if (stats.isFile()) return runPath; // unchanged file behaviour (compat)
154
+
155
+ if (stats.isSymbolicLink()) {
156
+ // A symlink that ultimately points at a directory would otherwise surface
157
+ // a raw EISDIR; resolve the target type and fail closed for directories.
158
+ let targetStats;
159
+ try {
160
+ targetStats = await stat(runPath);
161
+ } catch (err) {
162
+ if (err?.code === 'ENOENT') return runPath; // dangling: legacy error
163
+ throw new ReleaseError(
164
+ GATE_FAILED,
165
+ `cannot inspect release run path: ${err.message}`,
166
+ { runPath, cause: err.code },
167
+ );
168
+ }
169
+ if (targetStats.isDirectory()) {
170
+ throw new ReleaseError(
171
+ GATE_FAILED,
172
+ 'release run path is a symlinked directory; pass the run file (release-run.json) or the real run directory',
173
+ { runPath },
174
+ );
175
+ }
176
+ return runPath; // symlinked FILE keeps the legacy readFile behaviour
177
+ }
178
+
179
+ if (stats.isDirectory()) {
180
+ const candidate = join(runPath, 'release-run.json');
181
+ try {
182
+ const candidateStats = await lstat(candidate);
183
+ if (!candidateStats.isFile()) {
184
+ throw new ReleaseError(
185
+ GATE_FAILED,
186
+ `release run directory does not contain a regular release-run.json: ${runPath}`,
187
+ { runPath, expected: 'release-run.json' },
188
+ );
189
+ }
190
+ } catch (err) {
191
+ if (err instanceof ReleaseError) throw err;
192
+ throw new ReleaseError(
193
+ GATE_FAILED,
194
+ `release run directory must contain release-run.json: ${err.message}`,
195
+ { runPath, expected: 'release-run.json', cause: err.code },
196
+ );
197
+ }
198
+ return candidate;
199
+ }
200
+
201
+ return runPath; // anything else keeps the legacy readFile error path
202
+ }
203
+
123
204
  /**
124
205
  * Load, parse, and validate a run file from disk.
125
206
  *
126
- * @param {string} runPath - Absolute path to the run file.
207
+ * Accepts either the run file itself or its containing run directory (O6);
208
+ * a directory input resolves to `<dir>/release-run.json` via resolveRunPath.
209
+ *
210
+ * @param {string} runPath - Absolute path to the run file or run directory.
127
211
  * @returns {Promise<Object>} The validated run object.
128
212
  * @throws {ReleaseError} GATE_FAILED if the file cannot be read, parsed, or validated.
129
213
  */
130
214
  export async function loadRun(runPath, options = {}) {
215
+ const resolvedRunPath = await resolveRunPath(runPath);
131
216
  let raw;
132
217
  try {
133
- raw = await readFile(runPath, 'utf8');
218
+ raw = await readFile(resolvedRunPath, 'utf8');
134
219
  } catch (err) {
135
220
  throw new ReleaseError(
136
221
  GATE_FAILED,
137
222
  `cannot read release run: ${err.message}`,
138
- { runPath, cause: err.code },
223
+ { runPath: resolvedRunPath, cause: err.code },
139
224
  );
140
225
  }
141
226
 
@@ -146,13 +231,13 @@ export async function loadRun(runPath, options = {}) {
146
231
  throw new ReleaseError(
147
232
  GATE_FAILED,
148
233
  `release run is not valid JSON: ${err.message}`,
149
- { runPath },
234
+ { runPath: resolvedRunPath },
150
235
  );
151
236
  }
152
237
 
153
238
  validateRun(run, options);
154
239
  if (options.authorityPlanPath) {
155
- assertImmutableRunAuthority(runPath, options.authorityPlanPath, run);
240
+ assertImmutableRunAuthority(resolvedRunPath, options.authorityPlanPath, run);
156
241
  }
157
242
  return run;
158
243
  }