release-skill 0.1.4 → 0.1.6

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 (71) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/CHANGELOG.md +104 -0
  5. package/INSTALL.md +81 -1
  6. package/INSTALL.zh-CN.md +69 -1
  7. package/README.md +233 -8
  8. package/README.zh-CN.md +188 -8
  9. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  10. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  11. package/adapters/claude/bin/release-skill.bundle.mjs +14164 -9912
  12. package/adapters/claude/bin/release-skill.mjs +24 -4
  13. package/adapters/claude/native/safe-write/binding.gyp +2 -1
  14. package/adapters/claude/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  15. package/adapters/claude/native/safe-write/prebuilds.json +1 -1
  16. package/adapters/claude/schemas/.render-manifest.json +10 -10
  17. package/adapters/claude/schemas/release-project.schema.json +141 -0
  18. package/adapters/claude/skills/release-help/SKILL.md +21 -0
  19. package/adapters/claude/skills/release-prepare/SKILL.md +17 -6
  20. package/adapters/claude/skills/release-publish/SKILL.md +3 -1
  21. package/adapters/claude/skills/release-reconcile/SKILL.md +1 -1
  22. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  23. package/adapters/codex/bin/release-skill.bundle.mjs +14164 -9912
  24. package/adapters/codex/bin/release-skill.mjs +24 -4
  25. package/adapters/codex/native/safe-write/binding.gyp +2 -1
  26. package/adapters/codex/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  27. package/adapters/codex/native/safe-write/prebuilds.json +1 -1
  28. package/adapters/codex/schemas/.render-manifest.json +10 -10
  29. package/adapters/codex/schemas/release-project.schema.json +141 -0
  30. package/adapters/codex/skills/release-help/SKILL.md +21 -0
  31. package/adapters/codex/skills/release-prepare/SKILL.md +17 -6
  32. package/adapters/codex/skills/release-publish/SKILL.md +3 -1
  33. package/adapters/codex/skills/release-reconcile/SKILL.md +1 -1
  34. package/bin/release-skill-cli.mjs +163 -4
  35. package/bin/release-skill.bundle.mjs +14164 -9912
  36. package/bin/release-skill.mjs +24 -4
  37. package/native/safe-write/binding.gyp +2 -1
  38. package/native/safe-write/prebuilds/darwin-arm64/safe_write.node +0 -0
  39. package/native/safe-write/prebuilds.json +1 -1
  40. package/package.json +2 -2
  41. package/references/.render-manifest.json +4 -4
  42. package/references/02-project-config.md +24 -0
  43. package/references/05-evidence-and-errors.md +5 -0
  44. package/schemas/.render-manifest.json +10 -10
  45. package/schemas/release-project.schema.json +141 -0
  46. package/scripts/build-bundle.mjs +15 -2
  47. package/skills/release-help/SKILL.md +21 -0
  48. package/skills/release-prepare/SKILL.md +17 -6
  49. package/skills/release-publish/SKILL.md +3 -1
  50. package/skills/release-reconcile/SKILL.md +1 -1
  51. package/skills-src/release-help/SKILL.md +21 -0
  52. package/skills-src/release-prepare/SKILL.md +17 -6
  53. package/skills-src/release-publish/SKILL.md +3 -1
  54. package/skills-src/release-reconcile/SKILL.md +1 -1
  55. package/src/adapters/plugin-marketplace.mjs +70 -3
  56. package/src/artifacts/transaction-journal.mjs +1126 -105
  57. package/src/artifacts/transaction.mjs +313 -130
  58. package/src/commands/docs.mjs +332 -0
  59. package/src/commands/prepare.mjs +324 -17
  60. package/src/commands/reconcile.mjs +4 -1
  61. package/src/commands/verify.mjs +4 -1
  62. package/src/core/errors.mjs +64 -2
  63. package/src/core/plan.mjs +59 -1
  64. package/src/core/redact.mjs +206 -0
  65. package/src/docs/changelog-renderer.mjs +853 -0
  66. package/src/docs/config.mjs +337 -0
  67. package/src/docs/notes-loader.mjs +432 -0
  68. package/src/docs/notes.mjs +553 -0
  69. package/src/docs/readme-renderer.mjs +647 -0
  70. package/src/docs/refresh-planner.mjs +542 -0
  71. package/src/docs/refresh-service.mjs +675 -0
@@ -0,0 +1,332 @@
1
+ /**
2
+ * `release-skill docs` command module
3
+ * (2026-07-21-release-docs-command-and-prepare-gate §4,
4
+ * 2026-07-21-release-docs-refresh-protocol).
5
+ *
6
+ * `runDocsCommand({ subcommand, args, root })` is the single entry point used
7
+ * by the CLI router (bin/release-skill-cli.mjs). The docs command accepts
8
+ * exactly one subcommand — `refresh` — and validates every parameter BEFORE
9
+ * invoking the refresh service, so precise stable parameter errors surface
10
+ * even when no project configuration or safe-fs backend is available:
11
+ *
12
+ * - unknown/missing subcommand → MISSING_PARAMETERS {subcommand, valid}
13
+ * - bare positional (beyond the command
14
+ * and subcommand tokens) or single-dash
15
+ * flag (-w, -x) → MISSING_PARAMETERS UNRECOGNIZED_PARAMETER
16
+ * - missing --unit → MISSING_PARAMETERS {field:'--unit'}
17
+ * - flag without a value (or with a
18
+ * flag-like/-prefixed value) → MISSING_PARAMETERS MISSING_VALUE
19
+ * - --unit value outside the unit-id
20
+ * charset → MISSING_PARAMETERS INVALID_VALUE
21
+ * - duplicated flag → MISSING_PARAMETERS DUPLICATE_PARAMETER
22
+ * - malformed --confirm-refresh digest → MISSING_PARAMETERS INVALID_VALUE
23
+ * - write flags without --write → MISSING_PARAMETERS CONFLICTING_PARAMETERS
24
+ * - --write without both authorizations → MISSING_PARAMETERS MISSING_WRITE_PARAMETERS
25
+ * - any flag outside the recognized set → MISSING_PARAMETERS UNRECOGNIZED_PARAMETER
26
+ *
27
+ * The `--flag=value` equals form routes through exactly the same validation
28
+ * as the space-separated form: a valued flag's value is validated identically
29
+ * (errors name the flag part precisely, never the whole token), and boolean
30
+ * or router flags reject the equals form as INVALID_VALUE.
31
+ *
32
+ * The recognized flag set is exactly: --root --json --unit --write
33
+ * --confirm-refresh --ack-local-document-write. --root and --json are
34
+ * consumed by the CLI router (project root, output shape); the rest drive
35
+ * `runReleaseDocsRefresh`, which re-validates the three-way write
36
+ * authorization (write + exact confirmRefresh + ackLocalDocumentWrite) as
37
+ * defense in depth. Although the router owns --json's semantics, the
38
+ * space-separated form is duplicate-detected here: a repeated --json fails
39
+ * closed with DUPLICATE_PARAMETER BEFORE any service call, project config
40
+ * read, lock, or transaction — exactly like --unit, --write,
41
+ * --confirm-refresh, and --ack-local-document-write. The --json=value equals
42
+ * form stays rejected as INVALID_VALUE.
43
+ *
44
+ * The dry-run result is re-projected here so the authoritative version —
45
+ * deliberately kept off the service result's enumerable surface — becomes a
46
+ * regular JSON field of the CLI success shape. Write/clean results are
47
+ * returned verbatim. Every user-visible value carries canonical relative
48
+ * paths only: never absolute paths, note/old body text, credentials, full
49
+ * diffs, or serialized buffers.
50
+ *
51
+ * The write authorization granted by --write + --confirm-refresh +
52
+ * --ack-local-document-write covers ONLY the declared local release-document
53
+ * targets; it never authorizes committing, pushing, tagging, publishing, or
54
+ * installing.
55
+ *
56
+ * @module src/commands/docs
57
+ */
58
+
59
+ import {
60
+ ReleaseError,
61
+ MISSING_PARAMETERS,
62
+ } from '../core/errors.mjs';
63
+ import { runReleaseDocsRefresh } from '../docs/refresh-service.mjs';
64
+
65
+ const VALID_SUBCOMMANDS = Object.freeze(['refresh']);
66
+ const CONFIRM_REFRESH_PATTERN = /^sha256:[0-9a-f]{64}$/;
67
+
68
+ // Unit identifier charset — the same shape as the transaction journal's
69
+ // module-private DOCS_REFRESH_UNIT_ID_RE, deliberately re-declared locally
70
+ // (no cross-module import): start with an alphanumeric character, then
71
+ // alphanumerics, ".", "_" or "-".
72
+ const UNIT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
73
+
74
+ /**
75
+ * Parse and validate `docs refresh` parameters from the raw argv slice.
76
+ * All validation errors are MISSING_PARAMETERS with a machine-readable
77
+ * details.reason; validation completes before any service call.
78
+ *
79
+ * @param {string[]} args — Raw argv slice (includes the command and
80
+ * subcommand tokens; exactly those first two bare positionals are exempt,
81
+ * any further bare positional or single-dash flag is rejected).
82
+ * @returns {{ unitId: string, write: boolean, confirmRefresh: string|undefined,
83
+ * ackLocalDocumentWrite: boolean }}
84
+ * @throws {ReleaseError} MISSING_PARAMETERS on any parameter violation.
85
+ */
86
+ function parseDocsRefreshArgs(args) {
87
+ const argv = Array.isArray(args) ? args : [];
88
+ const seen = new Set();
89
+ let unitId;
90
+ let confirmRefresh;
91
+ let write = false;
92
+ let ackLocalDocumentWrite = false;
93
+ let barePositionals = 0;
94
+
95
+ const claimOnce = (flag) => {
96
+ if (seen.has(flag)) {
97
+ throw new ReleaseError(
98
+ MISSING_PARAMETERS,
99
+ `docs refresh received ${flag} more than once`,
100
+ { reason: 'DUPLICATE_PARAMETER', field: flag },
101
+ );
102
+ }
103
+ seen.add(flag);
104
+ };
105
+
106
+ // A flag value must be a non-empty token that is not itself flag-like
107
+ // (neither `--flag` nor `-x`); shared by the space-separated and the
108
+ // --flag=value forms.
109
+ const assertPlainValue = (flag, value) => {
110
+ if (typeof value !== 'string' || value.length === 0 || value.startsWith('-')) {
111
+ throw new ReleaseError(
112
+ MISSING_PARAMETERS,
113
+ `docs refresh ${flag} requires a value`,
114
+ { reason: 'MISSING_VALUE', field: flag },
115
+ );
116
+ }
117
+ return value;
118
+ };
119
+
120
+ const claimValue = (flag, index) => assertPlainValue(flag, argv[index + 1]);
121
+
122
+ const assertUnitId = (value) => {
123
+ if (!UNIT_ID_PATTERN.test(value)) {
124
+ throw new ReleaseError(
125
+ MISSING_PARAMETERS,
126
+ 'docs refresh --unit must be a unit identifier (start with an alphanumeric character; then alphanumerics, "." , "_" or "-" only)',
127
+ { reason: 'INVALID_VALUE', field: '--unit' },
128
+ );
129
+ }
130
+ return value;
131
+ };
132
+
133
+ const assertConfirmRefreshDigest = (value) => {
134
+ if (!CONFIRM_REFRESH_PATTERN.test(value)) {
135
+ throw new ReleaseError(
136
+ MISSING_PARAMETERS,
137
+ 'docs refresh --confirm-refresh must be exactly sha256:<64 lowercase hex characters>',
138
+ { reason: 'INVALID_VALUE', field: '--confirm-refresh' },
139
+ );
140
+ }
141
+ return value;
142
+ };
143
+
144
+ for (let i = 0; i < argv.length; i += 1) {
145
+ const token = argv[i];
146
+ if (typeof token !== 'string') continue;
147
+
148
+ if (token.startsWith('--')) {
149
+ // The --flag=value equals form routes through exactly the same
150
+ // validation as the space-separated form; errors name the flag part.
151
+ const eq = token.indexOf('=');
152
+ if (eq !== -1) {
153
+ const flag = token.slice(0, eq);
154
+ const inlineValue = token.slice(eq + 1);
155
+ if (flag === '--unit') {
156
+ claimOnce(flag);
157
+ unitId = assertUnitId(assertPlainValue(flag, inlineValue));
158
+ } else if (flag === '--confirm-refresh') {
159
+ claimOnce(flag);
160
+ confirmRefresh = assertConfirmRefreshDigest(assertPlainValue(flag, inlineValue));
161
+ } else if (
162
+ flag === '--write'
163
+ || flag === '--ack-local-document-write'
164
+ || flag === '--root'
165
+ || flag === '--json'
166
+ ) {
167
+ throw new ReleaseError(
168
+ MISSING_PARAMETERS,
169
+ `docs refresh ${flag} does not accept the --flag=value form`,
170
+ { reason: 'INVALID_VALUE', field: flag },
171
+ );
172
+ } else {
173
+ throw new ReleaseError(
174
+ MISSING_PARAMETERS,
175
+ `docs refresh does not accept ${flag}`,
176
+ { reason: 'UNRECOGNIZED_PARAMETER', parameter: flag },
177
+ );
178
+ }
179
+ continue;
180
+ }
181
+
182
+ if (token === '--unit') {
183
+ claimOnce(token);
184
+ unitId = assertUnitId(claimValue(token, i));
185
+ i += 1;
186
+ } else if (token === '--confirm-refresh') {
187
+ claimOnce(token);
188
+ confirmRefresh = assertConfirmRefreshDigest(claimValue(token, i));
189
+ i += 1;
190
+ } else if (token === '--write') {
191
+ claimOnce(token);
192
+ write = true;
193
+ } else if (token === '--ack-local-document-write') {
194
+ claimOnce(token);
195
+ ackLocalDocumentWrite = true;
196
+ } else if (token === '--json') {
197
+ // Consumed by the CLI router (output shape); no docs-command
198
+ // semantics, but the router-owned flag is still duplicate-detected
199
+ // here so a repeated space-separated --json fails closed with
200
+ // DUPLICATE_PARAMETER BEFORE any service call, config read, lock, or
201
+ // transaction — exactly like --unit/--write/--confirm-refresh/
202
+ // --ack-local-document-write.
203
+ claimOnce(token);
204
+ } else if (token === '--root') {
205
+ // Consumed by the CLI router (project root). Skip its value so the
206
+ // path is never mistaken for a bare positional; the router validates
207
+ // the --root value shape before calling runDocsCommand.
208
+ i += 1;
209
+ } else {
210
+ throw new ReleaseError(
211
+ MISSING_PARAMETERS,
212
+ `docs refresh does not accept ${token}`,
213
+ { reason: 'UNRECOGNIZED_PARAMETER', parameter: token },
214
+ );
215
+ }
216
+ continue;
217
+ }
218
+
219
+ if (token.startsWith('-') && token.length > 1) {
220
+ // Single-dash flags (e.g. -w, -x) were previously skipped silently.
221
+ throw new ReleaseError(
222
+ MISSING_PARAMETERS,
223
+ `docs refresh does not accept ${token}`,
224
+ { reason: 'UNRECOGNIZED_PARAMETER', parameter: token },
225
+ );
226
+ }
227
+
228
+ // The raw argv slice carries the `docs` command token and the subcommand
229
+ // token themselves; exempt exactly those first two bare positionals and
230
+ // reject any other positional (e.g. `docs refresh foo`).
231
+ barePositionals += 1;
232
+ if (barePositionals > 2) {
233
+ throw new ReleaseError(
234
+ MISSING_PARAMETERS,
235
+ `docs refresh does not accept positional argument ${token}`,
236
+ { reason: 'UNRECOGNIZED_PARAMETER', parameter: token },
237
+ );
238
+ }
239
+ }
240
+
241
+ if (typeof unitId !== 'string' || unitId.length === 0) {
242
+ throw new ReleaseError(
243
+ MISSING_PARAMETERS,
244
+ 'docs refresh requires --unit <id>',
245
+ { field: '--unit' },
246
+ );
247
+ }
248
+
249
+ if ((confirmRefresh !== undefined || ackLocalDocumentWrite) && !write) {
250
+ throw new ReleaseError(
251
+ MISSING_PARAMETERS,
252
+ 'docs refresh --confirm-refresh and --ack-local-document-write are only valid together with --write',
253
+ {
254
+ reason: 'CONFLICTING_PARAMETERS',
255
+ flags: ['--confirm-refresh', '--ack-local-document-write', '--write'],
256
+ },
257
+ );
258
+ }
259
+
260
+ if (write) {
261
+ const missing = [];
262
+ if (confirmRefresh === undefined) missing.push('confirmRefresh');
263
+ if (!ackLocalDocumentWrite) missing.push('ackLocalDocumentWrite');
264
+ if (missing.length > 0) {
265
+ throw new ReleaseError(
266
+ MISSING_PARAMETERS,
267
+ 'docs refresh --write requires --confirm-refresh <sha256:...> and --ack-local-document-write',
268
+ { reason: 'MISSING_WRITE_PARAMETERS', missing },
269
+ );
270
+ }
271
+ }
272
+
273
+ return { unitId, write, confirmRefresh, ackLocalDocumentWrite };
274
+ }
275
+
276
+ /**
277
+ * Run the `docs` command.
278
+ *
279
+ * @param {object} [options]
280
+ * @param {string} [options.subcommand] — Must be exactly 'refresh'.
281
+ * @param {string[]} [options.args] — Raw argv slice for parameter parsing.
282
+ * @param {string} [options.root] — Absolute project root (CLI resolves cwd).
283
+ * @returns {Promise<object>} Frozen docs-refresh result (CLI success shape):
284
+ * dry-run {command, mode, status, unitId, version, locales, inputDigest,
285
+ * refreshDigest, files, nextCommand}; write {command, mode, status,
286
+ * refreshed, unitId, version, refreshDigest, transactionId, refreshedPaths};
287
+ * clean write {command, mode, status, refreshed, unitId, version,
288
+ * refreshDigest}.
289
+ * @throws {ReleaseError} MISSING_PARAMETERS (24) on any parameter violation;
290
+ * all service error codes propagate unchanged (RELEASE_DOCS_INVALID 42,
291
+ * RELEASE_DOCS_REFRESH_STALE 45, GATE_FAILED 13, PATH_UNSAFE 28,
292
+ * SAFE_WRITE_UNAVAILABLE 39, PLAN_STALE 36, TRANSACTION_INCOMPLETE 38).
293
+ */
294
+ export async function runDocsCommand({ subcommand, args, root } = {}) {
295
+ if (subcommand !== 'refresh') {
296
+ throw new ReleaseError(
297
+ MISSING_PARAMETERS,
298
+ `unknown docs subcommand: "${typeof subcommand === 'string' ? subcommand : ''}"; valid: ${VALID_SUBCOMMANDS.join(', ')}`,
299
+ { subcommand, valid: [...VALID_SUBCOMMANDS] },
300
+ );
301
+ }
302
+
303
+ const options = parseDocsRefreshArgs(args);
304
+
305
+ const result = await runReleaseDocsRefresh({
306
+ root,
307
+ unitId: options.unitId,
308
+ write: options.write,
309
+ confirmRefresh: options.confirmRefresh,
310
+ ackLocalDocumentWrite: options.ackLocalDocumentWrite,
311
+ });
312
+
313
+ if (result.mode === 'dry-run') {
314
+ // The service keeps the authoritative version off the enumerable
315
+ // projection surface; the CLI success shape re-adds it as a regular
316
+ // field (contract §4.3 dry-run shape).
317
+ return Object.freeze({
318
+ command: result.command,
319
+ mode: result.mode,
320
+ status: result.status,
321
+ unitId: result.unitId,
322
+ version: result.version,
323
+ locales: result.locales,
324
+ inputDigest: result.inputDigest,
325
+ refreshDigest: result.refreshDigest,
326
+ files: result.files,
327
+ nextCommand: result.nextCommand,
328
+ });
329
+ }
330
+
331
+ return result;
332
+ }