release-skill 0.9.6 → 0.9.8
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 +9 -0
- package/.claude-plugin/marketplace.json +11 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +55 -0
- package/INSTALL.md +17 -9
- package/INSTALL.zh-CN.md +15 -9
- package/README.md +33 -28
- package/README.zh-CN.md +28 -26
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +357 -52
- package/adapters/claude/skills/release-finish/SKILL.md +12 -4
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +357 -52
- package/adapters/codex/skills/release-finish/SKILL.md +12 -4
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +357 -52
- package/adapters/kimi/skills/release-finish/SKILL.md +12 -4
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +357 -52
- package/adapters/workbuddy/skills/release-finish/SKILL.md +12 -4
- package/bin/release-skill.bundle.mjs +357 -52
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/skills/release-finish/SKILL.md +12 -4
- package/skills-src/release-finish/SKILL.md +12 -4
- package/src/commands/post-release-local.mjs +274 -39
- package/src/commands/prepare.mjs +5 -3
- package/src/commands/publish.mjs +30 -3
- package/src/commands/ship.mjs +4 -6
- package/src/commands/verify.mjs +22 -15
- package/src/core/postpublish.mjs +24 -0
- package/src/core/recovery.mjs +8 -6
- package/src/core/skill-resource-closure.mjs +19 -1
- package/src/core/surface-host-bindings.mjs +35 -0
package/src/commands/verify.mjs
CHANGED
|
@@ -23,7 +23,11 @@ import { resolveContained } from 'skill-family-harness-node';
|
|
|
23
23
|
const execFile = promisify(execFileCb);
|
|
24
24
|
|
|
25
25
|
import { validatePlan, computePlanDigest, validatePlanActionCompleteness } from '../core/plan.mjs';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
normalizePostPublishView,
|
|
28
|
+
postPublishActionId,
|
|
29
|
+
requiresPostPublishDistribution,
|
|
30
|
+
} from '../core/postpublish.mjs';
|
|
27
31
|
import { createEvidenceWriter } from '../core/evidence.mjs';
|
|
28
32
|
import { readRunRecovery } from '../core/recovery.mjs';
|
|
29
33
|
import {
|
|
@@ -1230,15 +1234,11 @@ export async function verifyRelease(options) {
|
|
|
1230
1234
|
|
|
1231
1235
|
// =======================================================================
|
|
1232
1236
|
// Step 2b: Check for postPublish distribution requirement.
|
|
1233
|
-
//
|
|
1234
|
-
// PARTIAL distribute run passes only through the blocksVerified:false
|
|
1237
|
+
// The gate triggers only when targets or distribute-phase hooks exist.
|
|
1238
|
+
// A PARTIAL distribute run passes only through the blocksVerified:false
|
|
1235
1239
|
// exemption path (evaluateDistributeGateRun) — warned, never silent.
|
|
1236
1240
|
// =======================================================================
|
|
1237
|
-
|
|
1238
|
-
// requirement; legacy absent postPublish resolves to the same empty view.
|
|
1239
|
-
const postPublishDeclarations = normalizePostPublishView(plan);
|
|
1240
|
-
const requiresDistribution = postPublishDeclarations.some((declaration) =>
|
|
1241
|
-
(declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
|
|
1241
|
+
const requiresDistribution = requiresPostPublishDistribution(plan);
|
|
1242
1242
|
if (requiresDistribution) {
|
|
1243
1243
|
await evidence.append({ phase: 'verify', step: 'distribute-run-discovery', status: 'started' });
|
|
1244
1244
|
|
|
@@ -2052,11 +2052,12 @@ export async function verifyRelease(options) {
|
|
|
2052
2052
|
await evidence.append({ phase: 'verify', step: 'skill-resource-closure', status: 'started' });
|
|
2053
2053
|
|
|
2054
2054
|
// Collect install paths from marketplace adapter checks.
|
|
2055
|
-
// R-13 P2 (ruling 6):
|
|
2056
|
-
//
|
|
2057
|
-
//
|
|
2058
|
-
//
|
|
2059
|
-
//
|
|
2055
|
+
// R-13 P2 (ruling 6): prepare and publish derive surface bindings in
|
|
2056
|
+
// frozen snapshot-root coordinates. An adapter observation is already
|
|
2057
|
+
// rooted at the installed plugin, so verify derives the same manifest
|
|
2058
|
+
// skills fact relative to '.' at that consumer coordinate. Plans
|
|
2059
|
+
// without a frozen installation contract (legacy) fall back to the
|
|
2060
|
+
// plugin-root surface ('.') with the legacy permissive checks.
|
|
2060
2061
|
const installSurfaces = [];
|
|
2061
2062
|
for (const check of adapterChecks) {
|
|
2062
2063
|
const platform = PLATFORMS.find((p) => p.actionType === check.actionType);
|
|
@@ -2065,11 +2066,16 @@ export async function verifyRelease(options) {
|
|
|
2065
2066
|
const unit = (plan.units ?? []).find((u) => u.id === unitId);
|
|
2066
2067
|
const dist = unit?.distributions?.find((d) => d.type === platform.distributionType);
|
|
2067
2068
|
let binding = null;
|
|
2069
|
+
let frozenPluginRoot = null;
|
|
2068
2070
|
const contract = dist?.installationContract;
|
|
2069
2071
|
if (contract?.normalizedManifest) {
|
|
2072
|
+
frozenPluginRoot = pluginRootFromManifestRelativePath(contract.manifestRelativePath);
|
|
2070
2073
|
binding = await deriveSurfaceHostBinding({
|
|
2071
2074
|
manifest: contract.normalizedManifest,
|
|
2072
|
-
|
|
2075
|
+
// Adapter observations are already rooted at the installed
|
|
2076
|
+
// plugin. The frozen manifest's skills path is therefore
|
|
2077
|
+
// relative to '.', not to the snapshot-root manifest path.
|
|
2078
|
+
pluginRoot: '.',
|
|
2073
2079
|
platform,
|
|
2074
2080
|
snapshotDir: check.observation.installPath,
|
|
2075
2081
|
});
|
|
@@ -2082,6 +2088,7 @@ export async function verifyRelease(options) {
|
|
|
2082
2088
|
unitId,
|
|
2083
2089
|
surfaceId: binding?.surfaceId ?? '.',
|
|
2084
2090
|
binding,
|
|
2091
|
+
frozenPluginRoot,
|
|
2085
2092
|
unit,
|
|
2086
2093
|
});
|
|
2087
2094
|
}
|
|
@@ -2192,7 +2199,7 @@ export async function verifyRelease(options) {
|
|
|
2192
2199
|
// publish's recheck uses.
|
|
2193
2200
|
const pluginDistCount = (surface.unit?.distributions ?? [])
|
|
2194
2201
|
.filter((d) => d.type !== 'npm').length;
|
|
2195
|
-
if (pluginDistCount === 1) {
|
|
2202
|
+
if (pluginDistCount === 1 && surface.frozenPluginRoot === '.') {
|
|
2196
2203
|
const observedReceipt = createSkillResourceClosureReceipt(closureResult, {
|
|
2197
2204
|
unitId: surface.unitId,
|
|
2198
2205
|
preparedAt: expectedUnitReceipt.preparedAt ?? null,
|
package/src/core/postpublish.mjs
CHANGED
|
@@ -547,6 +547,30 @@ export function normalizePostPublishView(plan) {
|
|
|
547
547
|
);
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Decide whether a frozen plan has work for the distribute phase.
|
|
552
|
+
*
|
|
553
|
+
* A declaration requires distribute when it contains at least one legacy
|
|
554
|
+
* target, an explicit phase:distribute hook, or a hook whose omitted phase
|
|
555
|
+
* keeps the existing distribute default. phase:postVerify hooks belong only
|
|
556
|
+
* to the independent postVerify run and must not allocate an empty
|
|
557
|
+
* distribute predecessor.
|
|
558
|
+
*
|
|
559
|
+
* This is the single release-domain authority used by ship, verify, and
|
|
560
|
+
* recovery. Shape compatibility remains owned by normalizePostPublishView.
|
|
561
|
+
*
|
|
562
|
+
* @param {object} plan - Frozen release plan.
|
|
563
|
+
* @returns {boolean} Whether distribute must precede verify.
|
|
564
|
+
*/
|
|
565
|
+
export function requiresPostPublishDistribution(plan) {
|
|
566
|
+
return normalizePostPublishView(plan).some((declaration) => (
|
|
567
|
+
(declaration.targets?.length ?? 0) > 0
|
|
568
|
+
|| (declaration.hooks ?? []).some((hook) => (
|
|
569
|
+
hook.phase === undefined || hook.phase === 'distribute'
|
|
570
|
+
))
|
|
571
|
+
));
|
|
572
|
+
}
|
|
573
|
+
|
|
550
574
|
/**
|
|
551
575
|
* Array-level domain validation: every EXPLICIT hooks[].id must be unique
|
|
552
576
|
* across the whole declaration array (multi-release-unit postPublish v3,
|
package/src/core/recovery.mjs
CHANGED
|
@@ -33,7 +33,12 @@ import { validatePlan, computePlanDigest, assertImmutablePlanAuthority } from '.
|
|
|
33
33
|
import { validateApproval, validateApprovalRecordSchema, assertImmutableApprovalAuthority, computeApprovalDigest } from './approval.mjs';
|
|
34
34
|
import { isMarketplaceAction } from './checkpoints.mjs';
|
|
35
35
|
import { ReleaseError, GATE_FAILED } from './errors.mjs';
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
effectiveHookRequiresApproval,
|
|
38
|
+
normalizePostPublishView,
|
|
39
|
+
postPublishActionId,
|
|
40
|
+
requiresPostPublishDistribution,
|
|
41
|
+
} from './postpublish.mjs';
|
|
37
42
|
import { assertPostPublishApprovalAuthority, validatePostPublishApproval } from './postpublish-approval.mjs';
|
|
38
43
|
|
|
39
44
|
/** Remote errors alone are insufficient to select a safe recovery phase. */
|
|
@@ -274,11 +279,8 @@ export async function readRunRecovery(runPath, options = {}) {
|
|
|
274
279
|
}
|
|
275
280
|
} else if (['publish', 'reconcile'].includes(command)) {
|
|
276
281
|
if (run.status === 'PUBLISHED') {
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
// distribution phase.
|
|
280
|
-
const needsDistribution = normalizePostPublishView(plan).some((declaration) =>
|
|
281
|
-
(declaration.targets?.length ?? 0) > 0 || (declaration.hooks?.length ?? 0) > 0);
|
|
282
|
+
// phase:postVerify hooks do not create an empty distribute predecessor.
|
|
283
|
+
const needsDistribution = requiresPostPublishDistribution(plan);
|
|
282
284
|
code = needsDistribution ? 'DISTRIBUTE' : 'VERIFY';
|
|
283
285
|
} else if (run.status === 'PARTIAL') {
|
|
284
286
|
code = 'RECONCILE';
|
|
@@ -742,9 +742,27 @@ export async function checkSkillResourceClosure({
|
|
|
742
742
|
* @param {Array<{ id: string, host: string, skillCount: number }>} surfaces
|
|
743
743
|
* @returns {{ passed: boolean, missing: Array<{ host: string, skillCount: number }> }}
|
|
744
744
|
*/
|
|
745
|
-
export function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces) {
|
|
745
|
+
export function evaluateDeclaredHostSurfaceCoverage(expectedHosts, surfaces, coverageClaims = []) {
|
|
746
746
|
const missing = [];
|
|
747
|
+
const observedById = new Map((surfaces ?? []).map((surface) => [surface.id, surface]));
|
|
748
|
+
const claimsByHost = new Map();
|
|
749
|
+
for (const claim of coverageClaims ?? []) {
|
|
750
|
+
const hostClaims = claimsByHost.get(claim.host) ?? [];
|
|
751
|
+
hostClaims.push(claim);
|
|
752
|
+
claimsByHost.set(claim.host, hostClaims);
|
|
753
|
+
}
|
|
747
754
|
for (const expectedHost of [...new Set(expectedHosts ?? [])].sort((a, b) => a.localeCompare(b))) {
|
|
755
|
+
const declaredClaims = claimsByHost.get(expectedHost) ?? [];
|
|
756
|
+
if (declaredClaims.length > 0) {
|
|
757
|
+
const missingClaim = declaredClaims.find((claim) => {
|
|
758
|
+
const surface = observedById.get(claim.surfaceId);
|
|
759
|
+
return !surface || !(surface.skillCount >= 1);
|
|
760
|
+
});
|
|
761
|
+
if (!missingClaim) continue;
|
|
762
|
+
const surface = observedById.get(missingClaim.surfaceId);
|
|
763
|
+
missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
748
766
|
const surface = (surfaces ?? []).find((item) => item.host === expectedHost);
|
|
749
767
|
if (!surface || !(surface.skillCount >= 1)) {
|
|
750
768
|
missing.push({ host: expectedHost, skillCount: surface?.skillCount ?? 0 });
|
|
@@ -186,3 +186,38 @@ export async function deriveSurfaceHostBinding({ manifest, pluginRoot, platform,
|
|
|
186
186
|
const host = await normalizeHostId(platform.buildAdapter.name);
|
|
187
187
|
return { surfaceId, host };
|
|
188
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Group manifest-backed claims before invoking the closure checker.
|
|
192
|
+
*
|
|
193
|
+
* A shared canonical source surface is scanned once. Claims remain complete
|
|
194
|
+
* for host coverage, while checker overrides are passed only for an
|
|
195
|
+
* unshared surface; this keeps the checker fail-closed when called directly
|
|
196
|
+
* with conflicting host claims.
|
|
197
|
+
*
|
|
198
|
+
* @param {Array<{surfaceId: string, host: string}>} claims
|
|
199
|
+
* @returns {{ coverageClaims: Array<{surfaceId: string, host: string}>, checkerBindings: Array<{surfaceId: string, host: string}> }}
|
|
200
|
+
*/
|
|
201
|
+
export function groupSurfaceHostBindings(claims = []) {
|
|
202
|
+
if (!Array.isArray(claims)) {
|
|
203
|
+
throw new TypeError('surfaceHostBindings must be an array');
|
|
204
|
+
}
|
|
205
|
+
const bySurface = new Map();
|
|
206
|
+
for (const claim of claims) {
|
|
207
|
+
if (!claim || typeof claim !== 'object'
|
|
208
|
+
|| typeof claim.surfaceId !== 'string' || typeof claim.host !== 'string') {
|
|
209
|
+
throw new TypeError('surfaceHostBindings entries must be objects with string surfaceId and host');
|
|
210
|
+
}
|
|
211
|
+
const existing = bySurface.get(claim.surfaceId) ?? [];
|
|
212
|
+
existing.push({ surfaceId: claim.surfaceId, host: claim.host });
|
|
213
|
+
bySurface.set(claim.surfaceId, existing);
|
|
214
|
+
}
|
|
215
|
+
const coverageClaims = [...bySurface.values()]
|
|
216
|
+
.flat()
|
|
217
|
+
.sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
|
|
218
|
+
const checkerBindings = [...bySurface.values()]
|
|
219
|
+
.filter((surfaceClaims) => surfaceClaims.length === 1)
|
|
220
|
+
.map(([claim]) => claim)
|
|
221
|
+
.sort((left, right) => left.surfaceId.localeCompare(right.surfaceId) || left.host.localeCompare(right.host));
|
|
222
|
+
return { coverageClaims, checkerBindings };
|
|
223
|
+
}
|