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.
- package/.agents/plugins/marketplace.json +23 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +10 -0
- package/.codex-plugin/plugin.json +26 -0
- package/CHANGELOG.md +68 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/CONTRIBUTING.md +49 -0
- package/INSTALL.md +182 -0
- package/LICENSE +21 -0
- package/NOTICE +25 -0
- package/README.md +501 -0
- package/README.zh-CN.md +463 -0
- package/SECURITY.md +48 -0
- package/adapters/claude/.claude-plugin/marketplace.json +16 -0
- package/adapters/claude/.claude-plugin/plugin.json +10 -0
- package/adapters/claude/skills/release-assess/SKILL.md +52 -0
- package/adapters/claude/skills/release-help/SKILL.md +60 -0
- package/adapters/claude/skills/release-prepare/SKILL.md +71 -0
- package/adapters/claude/skills/release-publish/SKILL.md +55 -0
- package/adapters/claude/skills/release-reconcile/SKILL.md +73 -0
- package/adapters/claude/skills/release-verify/SKILL.md +70 -0
- package/adapters/codex/.codex-plugin/plugin.json +26 -0
- package/adapters/codex/skills/release-assess/SKILL.md +52 -0
- package/adapters/codex/skills/release-help/SKILL.md +60 -0
- package/adapters/codex/skills/release-prepare/SKILL.md +71 -0
- package/adapters/codex/skills/release-publish/SKILL.md +55 -0
- package/adapters/codex/skills/release-reconcile/SKILL.md +73 -0
- package/adapters/codex/skills/release-verify/SKILL.md +70 -0
- package/bin/release-skill.mjs +743 -0
- package/native/safe-write/binding.gyp +40 -0
- package/native/safe-write/prebuilds.json +4 -0
- package/native/safe-write/src/safe_write.cc +2023 -0
- package/package.json +75 -0
- package/references/.render-manifest.json +33 -0
- package/references/00-target-state.md +124 -0
- package/references/01-state-machine.md +155 -0
- package/references/02-project-config.md +217 -0
- package/references/03-readme-quality.md +136 -0
- package/references/04-supply-chain.md +147 -0
- package/references/05-evidence-and-errors.md +164 -0
- package/references/06-adapter-contract.md +178 -0
- package/schemas/.render-manifest.json +37 -0
- package/schemas/approval-record.schema.json +115 -0
- package/schemas/artifact-lock.schema.json +111 -0
- package/schemas/artifact-plan.schema.json +52 -0
- package/schemas/artifact-policy.schema.json +76 -0
- package/schemas/evidence-event.schema.json +89 -0
- package/schemas/release-plan.schema.json +369 -0
- package/schemas/release-project.schema.json +359 -0
- package/schemas/release-run.schema.json +195 -0
- package/skills/release-assess/SKILL.md +52 -0
- package/skills/release-help/SKILL.md +60 -0
- package/skills/release-prepare/SKILL.md +71 -0
- package/skills/release-publish/SKILL.md +55 -0
- package/skills/release-reconcile/SKILL.md +73 -0
- package/skills/release-verify/SKILL.md +70 -0
- package/skills-src/release-assess/SKILL.md +52 -0
- package/skills-src/release-help/SKILL.md +60 -0
- package/skills-src/release-prepare/SKILL.md +71 -0
- package/skills-src/release-publish/SKILL.md +55 -0
- package/skills-src/release-reconcile/SKILL.md +73 -0
- package/skills-src/release-verify/SKILL.md +70 -0
- package/src/adapters/contract.mjs +214 -0
- package/src/adapters/git-github.mjs +214 -0
- package/src/adapters/npm.mjs +947 -0
- package/src/adapters/plugin-marketplace.mjs +1365 -0
- package/src/adapters/push-snapshot.mjs +216 -0
- package/src/artifacts/adoption.mjs +743 -0
- package/src/artifacts/artifact-plan.mjs +162 -0
- package/src/artifacts/entry.mjs +240 -0
- package/src/artifacts/git-authority.mjs +637 -0
- package/src/artifacts/graph.mjs +189 -0
- package/src/artifacts/inspect.mjs +520 -0
- package/src/artifacts/inventory.mjs +192 -0
- package/src/artifacts/merge/binary.mjs +77 -0
- package/src/artifacts/merge/entry-merge.mjs +228 -0
- package/src/artifacts/merge/json.mjs +641 -0
- package/src/artifacts/merge/markdown.mjs +246 -0
- package/src/artifacts/merge/regions.mjs +156 -0
- package/src/artifacts/merge/text.mjs +432 -0
- package/src/artifacts/merge/tree.mjs +202 -0
- package/src/artifacts/merge/yaml.mjs +669 -0
- package/src/artifacts/path-key.mjs +94 -0
- package/src/artifacts/policy.mjs +319 -0
- package/src/artifacts/producer-registry.mjs +439 -0
- package/src/artifacts/project-lock.mjs +732 -0
- package/src/artifacts/resolution.mjs +658 -0
- package/src/artifacts/safe-fs-backend-internal.mjs +680 -0
- package/src/artifacts/safe-fs.mjs +72 -0
- package/src/artifacts/state.mjs +495 -0
- package/src/artifacts/transaction-journal.mjs +983 -0
- package/src/artifacts/transaction.mjs +1361 -0
- package/src/commands/approve.mjs +280 -0
- package/src/commands/artifacts.mjs +627 -0
- package/src/commands/assess.mjs +838 -0
- package/src/commands/prepare.mjs +1377 -0
- package/src/commands/publish.mjs +883 -0
- package/src/commands/reconcile.mjs +1255 -0
- package/src/commands/verify.mjs +915 -0
- package/src/core/approval.mjs +332 -0
- package/src/core/baseline.mjs +272 -0
- package/src/core/blackbox-hard-gates.mjs +142 -0
- package/src/core/config.mjs +448 -0
- package/src/core/digest.mjs +90 -0
- package/src/core/errors.mjs +113 -0
- package/src/core/evidence.mjs +167 -0
- package/src/core/hooks.mjs +241 -0
- package/src/core/node-version.mjs +64 -0
- package/src/core/plan.mjs +735 -0
- package/src/core/previous-public-baseline.mjs +204 -0
- package/src/core/run.mjs +681 -0
- package/src/core/state-machine.mjs +76 -0
- package/src/core/version-consistency.mjs +111 -0
- package/src/producers/build-adapters.mjs +231 -0
- package/src/producers/render-public-assets.mjs +152 -0
- package/src/producers/sync-skills.mjs +96 -0
- package/src/readme/contract.mjs +297 -0
- package/src/readme/examples.mjs +288 -0
- package/src/readme/parity.mjs +122 -0
- package/src/snapshot/export.mjs +99 -0
- package/src/snapshot/frozen.mjs +401 -0
- package/src/snapshot/manifest.mjs +207 -0
- package/src/snapshot/public-map.mjs +1459 -0
- package/src/snapshot/public-path.mjs +110 -0
- 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
|
+
}
|