release-skill 0.1.1

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 (125) hide show
  1. package/.agents/plugins/marketplace.json +23 -0
  2. package/.claude-plugin/marketplace.json +16 -0
  3. package/.claude-plugin/plugin.json +10 -0
  4. package/.codex-plugin/plugin.json +26 -0
  5. package/CHANGELOG.md +68 -0
  6. package/CODE_OF_CONDUCT.md +76 -0
  7. package/CONTRIBUTING.md +49 -0
  8. package/INSTALL.md +182 -0
  9. package/LICENSE +21 -0
  10. package/NOTICE +25 -0
  11. package/README.md +501 -0
  12. package/README.zh-CN.md +463 -0
  13. package/SECURITY.md +48 -0
  14. package/adapters/claude/.claude-plugin/marketplace.json +16 -0
  15. package/adapters/claude/.claude-plugin/plugin.json +10 -0
  16. package/adapters/claude/skills/release-assess/SKILL.md +52 -0
  17. package/adapters/claude/skills/release-help/SKILL.md +60 -0
  18. package/adapters/claude/skills/release-prepare/SKILL.md +71 -0
  19. package/adapters/claude/skills/release-publish/SKILL.md +55 -0
  20. package/adapters/claude/skills/release-reconcile/SKILL.md +73 -0
  21. package/adapters/claude/skills/release-verify/SKILL.md +70 -0
  22. package/adapters/codex/.codex-plugin/plugin.json +26 -0
  23. package/adapters/codex/skills/release-assess/SKILL.md +52 -0
  24. package/adapters/codex/skills/release-help/SKILL.md +60 -0
  25. package/adapters/codex/skills/release-prepare/SKILL.md +71 -0
  26. package/adapters/codex/skills/release-publish/SKILL.md +55 -0
  27. package/adapters/codex/skills/release-reconcile/SKILL.md +73 -0
  28. package/adapters/codex/skills/release-verify/SKILL.md +70 -0
  29. package/bin/release-skill.mjs +743 -0
  30. package/native/safe-write/binding.gyp +40 -0
  31. package/native/safe-write/prebuilds.json +4 -0
  32. package/native/safe-write/src/safe_write.cc +2023 -0
  33. package/package.json +75 -0
  34. package/references/.render-manifest.json +33 -0
  35. package/references/00-target-state.md +124 -0
  36. package/references/01-state-machine.md +155 -0
  37. package/references/02-project-config.md +217 -0
  38. package/references/03-readme-quality.md +136 -0
  39. package/references/04-supply-chain.md +147 -0
  40. package/references/05-evidence-and-errors.md +164 -0
  41. package/references/06-adapter-contract.md +178 -0
  42. package/schemas/.render-manifest.json +37 -0
  43. package/schemas/approval-record.schema.json +115 -0
  44. package/schemas/artifact-lock.schema.json +111 -0
  45. package/schemas/artifact-plan.schema.json +52 -0
  46. package/schemas/artifact-policy.schema.json +76 -0
  47. package/schemas/evidence-event.schema.json +89 -0
  48. package/schemas/release-plan.schema.json +369 -0
  49. package/schemas/release-project.schema.json +359 -0
  50. package/schemas/release-run.schema.json +195 -0
  51. package/skills/release-assess/SKILL.md +52 -0
  52. package/skills/release-help/SKILL.md +60 -0
  53. package/skills/release-prepare/SKILL.md +71 -0
  54. package/skills/release-publish/SKILL.md +55 -0
  55. package/skills/release-reconcile/SKILL.md +73 -0
  56. package/skills/release-verify/SKILL.md +70 -0
  57. package/skills-src/release-assess/SKILL.md +52 -0
  58. package/skills-src/release-help/SKILL.md +60 -0
  59. package/skills-src/release-prepare/SKILL.md +71 -0
  60. package/skills-src/release-publish/SKILL.md +55 -0
  61. package/skills-src/release-reconcile/SKILL.md +73 -0
  62. package/skills-src/release-verify/SKILL.md +70 -0
  63. package/src/adapters/contract.mjs +214 -0
  64. package/src/adapters/git-github.mjs +214 -0
  65. package/src/adapters/npm.mjs +947 -0
  66. package/src/adapters/plugin-marketplace.mjs +1365 -0
  67. package/src/adapters/push-snapshot.mjs +216 -0
  68. package/src/artifacts/adoption.mjs +743 -0
  69. package/src/artifacts/artifact-plan.mjs +162 -0
  70. package/src/artifacts/entry.mjs +240 -0
  71. package/src/artifacts/git-authority.mjs +637 -0
  72. package/src/artifacts/graph.mjs +189 -0
  73. package/src/artifacts/inspect.mjs +520 -0
  74. package/src/artifacts/inventory.mjs +192 -0
  75. package/src/artifacts/merge/binary.mjs +77 -0
  76. package/src/artifacts/merge/entry-merge.mjs +228 -0
  77. package/src/artifacts/merge/json.mjs +641 -0
  78. package/src/artifacts/merge/markdown.mjs +246 -0
  79. package/src/artifacts/merge/regions.mjs +156 -0
  80. package/src/artifacts/merge/text.mjs +432 -0
  81. package/src/artifacts/merge/tree.mjs +202 -0
  82. package/src/artifacts/merge/yaml.mjs +669 -0
  83. package/src/artifacts/path-key.mjs +94 -0
  84. package/src/artifacts/policy.mjs +319 -0
  85. package/src/artifacts/producer-registry.mjs +439 -0
  86. package/src/artifacts/project-lock.mjs +732 -0
  87. package/src/artifacts/resolution.mjs +658 -0
  88. package/src/artifacts/safe-fs-backend-internal.mjs +680 -0
  89. package/src/artifacts/safe-fs.mjs +72 -0
  90. package/src/artifacts/state.mjs +495 -0
  91. package/src/artifacts/transaction-journal.mjs +983 -0
  92. package/src/artifacts/transaction.mjs +1361 -0
  93. package/src/commands/approve.mjs +280 -0
  94. package/src/commands/artifacts.mjs +627 -0
  95. package/src/commands/assess.mjs +838 -0
  96. package/src/commands/prepare.mjs +1377 -0
  97. package/src/commands/publish.mjs +883 -0
  98. package/src/commands/reconcile.mjs +1255 -0
  99. package/src/commands/verify.mjs +915 -0
  100. package/src/core/approval.mjs +332 -0
  101. package/src/core/baseline.mjs +272 -0
  102. package/src/core/blackbox-hard-gates.mjs +142 -0
  103. package/src/core/config.mjs +448 -0
  104. package/src/core/digest.mjs +90 -0
  105. package/src/core/errors.mjs +113 -0
  106. package/src/core/evidence.mjs +167 -0
  107. package/src/core/hooks.mjs +241 -0
  108. package/src/core/node-version.mjs +64 -0
  109. package/src/core/plan.mjs +735 -0
  110. package/src/core/previous-public-baseline.mjs +204 -0
  111. package/src/core/run.mjs +681 -0
  112. package/src/core/state-machine.mjs +76 -0
  113. package/src/core/version-consistency.mjs +111 -0
  114. package/src/producers/build-adapters.mjs +231 -0
  115. package/src/producers/render-public-assets.mjs +152 -0
  116. package/src/producers/sync-skills.mjs +96 -0
  117. package/src/readme/contract.mjs +297 -0
  118. package/src/readme/examples.mjs +288 -0
  119. package/src/readme/parity.mjs +122 -0
  120. package/src/snapshot/export.mjs +99 -0
  121. package/src/snapshot/frozen.mjs +401 -0
  122. package/src/snapshot/manifest.mjs +207 -0
  123. package/src/snapshot/public-map.mjs +1459 -0
  124. package/src/snapshot/public-path.mjs +110 -0
  125. package/src/snapshot/scan.mjs +419 -0
@@ -0,0 +1,204 @@
1
+ import { ReleaseError, GATE_FAILED } from './errors.mjs';
2
+
3
+ /**
4
+ * Observe whether the previous public baseline is still consistent with the
5
+ * expected state recorded in the release plan.
6
+ *
7
+ * This gate runs early in the release lifecycle to ensure that the upstream
8
+ * public artifact has not drifted since the plan was prepared.
9
+ *
10
+ * @param {object} opts
11
+ * @param {{ mode: 'none' } | { mode: 'bound', repo: string, ref: string, commit: string, tree?: string, manifestDigest?: string }} opts.baseline
12
+ * The previous-public-baseline specification from the release plan.
13
+ * @param {(repo: string, ref: string, commit: string) => Promise<{ status: 'consistent' | 'drifted' | 'unknown', actual?: string, diff?: string, error?: string }>} opts.observeFn
14
+ * Async function that queries the actual remote state.
15
+ * @param {{ append: (record: Record<string, unknown>) => void }} opts.evidence
16
+ * Evidence collector for audit trail.
17
+ * @returns {Promise<{ consistent: true, observed?: Record<string, unknown> }>}
18
+ * @throws {ReleaseError} When the baseline has drifted or the mode is invalid.
19
+ */
20
+ export async function observePreviousPublicBaseline({ baseline, observeFn, evidence }) {
21
+ if (!baseline || typeof baseline !== 'object') {
22
+ throw new ReleaseError(
23
+ GATE_FAILED,
24
+ 'previousPublicBaseline.mode must be "none" or "bound"',
25
+ );
26
+ }
27
+
28
+ const { mode } = baseline;
29
+
30
+ if (mode === 'none') {
31
+ evidence?.append({
32
+ phase: 'previous-public-baseline',
33
+ status: 'skipped',
34
+ reason: 'fresh repository',
35
+ });
36
+ return { consistent: true };
37
+ }
38
+
39
+ if (mode === 'bound') {
40
+ const { githubHost, repo, ref, commit } = baseline;
41
+ const result = await observeFn(repo, ref, commit, { githubHost });
42
+
43
+ if (result.status === 'consistent') {
44
+ evidence?.append({
45
+ phase: 'previous-public-baseline',
46
+ status: 'consistent',
47
+ repo,
48
+ ref,
49
+ commit,
50
+ });
51
+ return { consistent: true, observed: { ...result } };
52
+ }
53
+
54
+ if (result.status === 'drifted') {
55
+ throw new ReleaseError(
56
+ GATE_FAILED,
57
+ `previous public baseline drifted: expected commit ${commit} got ${result.actual ?? 'unknown'}`,
58
+ { expected: commit, actual: result.actual, diff: result.diff },
59
+ );
60
+ }
61
+
62
+ // 'unknown' or any other unexpected status
63
+ throw new ReleaseError(
64
+ GATE_FAILED,
65
+ `previous public baseline unknown: ${result.error ?? 'unrecognised status'}`,
66
+ { error: result.error, status: result.status },
67
+ );
68
+ }
69
+
70
+ // Invalid or missing mode
71
+ throw new ReleaseError(
72
+ GATE_FAILED,
73
+ 'previousPublicBaseline.mode must be "none" or "bound"',
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Re-observe the previous public baseline during reconcile. Unlike the
79
+ * primary observe, this returns a soft result instead of throwing so the
80
+ * caller can decide how to surface the inconsistency.
81
+ *
82
+ * @param {object} opts
83
+ * @param {{ mode: string, repo?: string, ref?: string, commit?: string } | undefined | null} opts.baseline
84
+ * The per-unit previous-public-baseline from the frozen plan.
85
+ * @param {(repo: string, ref: string, commit: string) => Promise<{ status: 'consistent' | 'drifted' | 'unknown', actual?: string, diff?: string, error?: string }>} opts.observeFn
86
+ * Async function that queries the actual remote state.
87
+ * @param {{ append: (record: Record<string, unknown>) => void }} opts.evidence
88
+ * Evidence collector for audit trail.
89
+ * @returns {Promise<{ consistent: true } | { consistent: false, error: string, detail?: Record<string, unknown> }>}
90
+ */
91
+ export async function reObservePreviousPublicBaseline({ baseline, observeFn, evidence }) {
92
+ if (!baseline || typeof baseline !== 'object') {
93
+ return { consistent: true };
94
+ }
95
+
96
+ const { mode } = baseline;
97
+
98
+ if (mode === 'none') {
99
+ return { consistent: true };
100
+ }
101
+
102
+ if (mode === 'bound') {
103
+ const { githubHost, repo, ref, commit } = baseline;
104
+ const result = await observeFn(repo, ref, commit, { githubHost });
105
+
106
+ if (result.status === 'consistent') {
107
+ evidence?.append({
108
+ phase: 're-observe-previous-public-baseline',
109
+ status: 'consistent',
110
+ repo,
111
+ ref,
112
+ commit,
113
+ });
114
+ return { consistent: true };
115
+ }
116
+
117
+ // Drifted or unknown -- return soft failure for reconcile to handle
118
+ return {
119
+ consistent: false,
120
+ error: 'previous public baseline changed since plan freeze',
121
+ detail: {
122
+ expected: commit,
123
+ actual: result.actual,
124
+ diff: result.diff,
125
+ error: result.error,
126
+ status: result.status,
127
+ },
128
+ };
129
+ }
130
+
131
+ // Unknown mode -- treat as soft failure during reconcile
132
+ return {
133
+ consistent: false,
134
+ error: 'previous public baseline changed since plan freeze',
135
+ detail: { reason: `unrecognised mode: ${mode}` },
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Validate the shape of a previous-public-baseline configuration object
141
+ * without executing any observation.
142
+ *
143
+ * @param {unknown} baseline - The baseline configuration to validate.
144
+ * @returns {{ valid: true } | { valid: false, errors: string[] }}
145
+ */
146
+ export function validatePreviousPublicBaselineConfig(baseline) {
147
+ const errors = [];
148
+
149
+ if (baseline == null || typeof baseline !== 'object') {
150
+ return { valid: false, errors: ['baseline must be a non-null object'] };
151
+ }
152
+
153
+ if (baseline.mode !== 'none' && baseline.mode !== 'bound') {
154
+ errors.push('mode must be "none" or "bound"');
155
+ }
156
+
157
+ if (baseline.mode === 'none') {
158
+ const forbidden = ['githubHost', 'repo', 'ref', 'commit', 'tree', 'manifestDigest'];
159
+ for (const field of forbidden) {
160
+ if (baseline[field] !== undefined) {
161
+ errors.push(`mode "none" must not include "${field}"`);
162
+ }
163
+ }
164
+ }
165
+
166
+ if (baseline.mode === 'bound') {
167
+ if (baseline.githubHost !== undefined && (typeof baseline.githubHost !== 'string' || baseline.githubHost.length === 0)) {
168
+ errors.push('"githubHost" must be a non-empty string when provided');
169
+ }
170
+ if (typeof baseline.repo !== 'string' || baseline.repo.length === 0) {
171
+ errors.push('bound mode requires a non-empty string "repo"');
172
+ }
173
+ if (typeof baseline.ref !== 'string' || baseline.ref.length === 0) {
174
+ errors.push('bound mode requires a non-empty string "ref"');
175
+ }
176
+ if (typeof baseline.commit !== 'string' || baseline.commit.length === 0) {
177
+ errors.push('bound mode requires a non-empty string "commit"');
178
+ }
179
+ if (baseline.tree !== undefined && typeof baseline.tree !== 'string') {
180
+ errors.push('"tree" must be a string when provided');
181
+ }
182
+ if (baseline.manifestDigest !== undefined && typeof baseline.manifestDigest !== 'string') {
183
+ errors.push('"manifestDigest" must be a string when provided');
184
+ }
185
+ }
186
+
187
+ if (errors.length > 0) {
188
+ return { valid: false, errors };
189
+ }
190
+ return { valid: true };
191
+ }
192
+
193
+ export function assertPreviousPublicBaselineTarget({ baseline, githubHost, publicRepo, requireHost = false }) {
194
+ if (!baseline || baseline.mode !== 'bound') return;
195
+ if (baseline.repo !== publicRepo) {
196
+ throw new ReleaseError(GATE_FAILED, 'previous public baseline repo does not match the production repository');
197
+ }
198
+ if (requireHost && !baseline.githubHost) {
199
+ throw new ReleaseError(GATE_FAILED, 'production previous public baseline must freeze githubHost');
200
+ }
201
+ if (baseline.githubHost && baseline.githubHost !== githubHost) {
202
+ throw new ReleaseError(GATE_FAILED, 'previous public baseline host does not match the production GitHub host');
203
+ }
204
+ }