release-skill 0.2.4 → 0.2.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.
- package/.claude-plugin/marketplace.json +1 -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 +45 -0
- package/INSTALL.md +32 -8
- package/INSTALL.zh-CN.md +28 -6
- package/README.md +29 -15
- package/README.zh-CN.md +27 -15
- 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 +2202 -708
- package/adapters/claude/schemas/.render-manifest.json +6 -6
- package/adapters/claude/schemas/release-plan.schema.json +158 -0
- package/adapters/claude/schemas/release-project.schema.json +6 -0
- package/adapters/claude/schemas/release-run.schema.json +150 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +2202 -708
- package/adapters/codex/schemas/.render-manifest.json +6 -6
- package/adapters/codex/schemas/release-plan.schema.json +158 -0
- package/adapters/codex/schemas/release-project.schema.json +6 -0
- package/adapters/codex/schemas/release-run.schema.json +150 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +2202 -708
- package/adapters/kimi/schemas/.render-manifest.json +6 -6
- package/adapters/kimi/schemas/release-plan.schema.json +158 -0
- package/adapters/kimi/schemas/release-project.schema.json +6 -0
- package/adapters/kimi/schemas/release-run.schema.json +150 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +2202 -708
- package/adapters/workbuddy/schemas/.render-manifest.json +6 -6
- package/adapters/workbuddy/schemas/release-plan.schema.json +158 -0
- package/adapters/workbuddy/schemas/release-project.schema.json +6 -0
- package/adapters/workbuddy/schemas/release-run.schema.json +150 -0
- package/bin/release-skill.bundle.mjs +2202 -708
- package/package.json +1 -1
- package/references/.render-manifest.json +4 -4
- package/references/02-project-config.md +28 -2
- package/references/05-evidence-and-errors.md +6 -0
- package/schemas/.render-manifest.json +6 -6
- package/schemas/release-plan.schema.json +158 -0
- package/schemas/release-project.schema.json +6 -0
- package/schemas/release-run.schema.json +150 -0
- package/scripts/sync-public-files.mjs +20 -9
- package/src/adapters/plugin-marketplace.mjs +82 -3
- package/src/commands/prepare.mjs +232 -1
- package/src/commands/publish.mjs +134 -0
- package/src/commands/reconcile.mjs +19 -0
- package/src/commands/setup.mjs +26 -0
- package/src/commands/verify.mjs +183 -1
- package/src/core/errors.mjs +12 -0
- package/src/core/plan.mjs +29 -0
- package/src/core/skill-resource-closure.mjs +425 -0
- package/src/core/source-authority.mjs +547 -0
- package/src/platforms/codebuddy.mjs +16 -3
- package/src/platforms/kimi.mjs +36 -9
package/src/commands/verify.mjs
CHANGED
|
@@ -41,8 +41,10 @@ import {
|
|
|
41
41
|
import {
|
|
42
42
|
ReleaseError,
|
|
43
43
|
GATE_FAILED,
|
|
44
|
+
CONFIG_MISSING,
|
|
44
45
|
POST_PUBLISH_VERIFY_FAILED,
|
|
45
46
|
} from '../core/errors.mjs';
|
|
47
|
+
import { verifySourceAuthorityReceipt } from '../core/source-authority.mjs';
|
|
46
48
|
import { assertTransition, PUBLISHED, VERIFIED } from '../core/state-machine.mjs';
|
|
47
49
|
import { resolveUnitScopedPath } from '../snapshot/public-path.mjs';
|
|
48
50
|
import {
|
|
@@ -51,6 +53,11 @@ import {
|
|
|
51
53
|
resolveNpmRegistryAuthToken,
|
|
52
54
|
} from '../adapters/npm.mjs';
|
|
53
55
|
import { runConsumerVerificationGates } from '../core/verification-gates.mjs';
|
|
56
|
+
import {
|
|
57
|
+
checkSkillResourceClosure,
|
|
58
|
+
createSkillResourceClosureReceipt,
|
|
59
|
+
evaluateConsumerSkillResourceClosureReceipts,
|
|
60
|
+
} from '../core/skill-resource-closure.mjs';
|
|
54
61
|
import { isRemoteWriteAction, isMarketplaceAction } from '../core/checkpoints.mjs';
|
|
55
62
|
import {
|
|
56
63
|
shouldSkipVerification,
|
|
@@ -216,11 +223,13 @@ export async function runSmokeTest(plan, root, options = {}) {
|
|
|
216
223
|
skipped: true,
|
|
217
224
|
details: { message: 'No npm distributions in plan; smoke test skipped' },
|
|
218
225
|
gateResults: [],
|
|
226
|
+
skillResourceClosureReceipts: [],
|
|
219
227
|
};
|
|
220
228
|
}
|
|
221
229
|
|
|
222
230
|
const results = [];
|
|
223
231
|
const gateResults = [];
|
|
232
|
+
const skillResourceClosureReceipts = [];
|
|
224
233
|
|
|
225
234
|
for (const { package: pkgName, registry, targetVersion, unitId, smokeBin, smokeArgs, smokeExpectedJson } of npmDistributions) {
|
|
226
235
|
const packageAtVersion = `${pkgName}@${targetVersion}`;
|
|
@@ -285,6 +294,47 @@ export async function runSmokeTest(plan, root, options = {}) {
|
|
|
285
294
|
}
|
|
286
295
|
|
|
287
296
|
const pkgRoot = join(installDir, 'node_modules', pkgName);
|
|
297
|
+
if (plan.skillResourceClosure) {
|
|
298
|
+
const expectedUnitReceipt = plan.skillResourceClosure.unitReceipts
|
|
299
|
+
.find((item) => item.unitId === unitId);
|
|
300
|
+
if (!expectedUnitReceipt) {
|
|
301
|
+
return {
|
|
302
|
+
passed: false,
|
|
303
|
+
details: { error: `Skill resource closure receipt missing for npm unit "${unitId}"` },
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const closureResult = await checkSkillResourceClosure({
|
|
307
|
+
snapshotDir: pkgRoot,
|
|
308
|
+
host: 'npm',
|
|
309
|
+
});
|
|
310
|
+
if (closureResult.findings.length > 0) {
|
|
311
|
+
return {
|
|
312
|
+
passed: false,
|
|
313
|
+
details: {
|
|
314
|
+
error: `Skill resource closure failed for ${packageAtVersion}`,
|
|
315
|
+
findings: closureResult.findings,
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (expectedUnitReceipt.skillCount > 0 && closureResult.skillCount === 0) {
|
|
320
|
+
return {
|
|
321
|
+
passed: false,
|
|
322
|
+
details: {
|
|
323
|
+
error: `Installed npm package ${packageAtVersion} contains no skills`,
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
skillResourceClosureReceipts.push(createSkillResourceClosureReceipt(
|
|
328
|
+
closureResult,
|
|
329
|
+
{
|
|
330
|
+
unitId,
|
|
331
|
+
distribution: 'npm',
|
|
332
|
+
host: 'npm',
|
|
333
|
+
checkedAt: new Date().toISOString(),
|
|
334
|
+
exitCode: 0,
|
|
335
|
+
},
|
|
336
|
+
));
|
|
337
|
+
}
|
|
288
338
|
gateResults.push(...await runConsumerVerificationGates({
|
|
289
339
|
plan,
|
|
290
340
|
unitId,
|
|
@@ -471,6 +521,7 @@ export async function runSmokeTest(plan, root, options = {}) {
|
|
|
471
521
|
count: results.length,
|
|
472
522
|
},
|
|
473
523
|
gateResults,
|
|
524
|
+
skillResourceClosureReceipts,
|
|
474
525
|
};
|
|
475
526
|
} finally {
|
|
476
527
|
await rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
@@ -605,6 +656,13 @@ export async function verifyRelease(options) {
|
|
|
605
656
|
);
|
|
606
657
|
}
|
|
607
658
|
validatePlan(plan);
|
|
659
|
+
if (plan.production?.mode === 'github-npm-v1' && !plan.sourceAuthority) {
|
|
660
|
+
throw new ReleaseError(
|
|
661
|
+
CONFIG_MISSING,
|
|
662
|
+
'production verify requires a frozen sourceAuthority binding; the release must be re-prepared before publish',
|
|
663
|
+
{ gate: 'source-authority' },
|
|
664
|
+
);
|
|
665
|
+
}
|
|
608
666
|
|
|
609
667
|
// --- Set up directories ---
|
|
610
668
|
const runId = `verify-${Date.now()}`;
|
|
@@ -728,6 +786,29 @@ export async function verifyRelease(options) {
|
|
|
728
786
|
// Validate checkpoint mapping
|
|
729
787
|
validateRunCheckpointMapping(sourceRun, plan.externalActions ?? []);
|
|
730
788
|
|
|
789
|
+
// --- Source authority receipt verification ---
|
|
790
|
+
// If the plan declares sourceAuthority, the source publish run must
|
|
791
|
+
// contain a matching CONSISTENT receipt bound to this planDigest.
|
|
792
|
+
if (plan.sourceAuthority) {
|
|
793
|
+
await evidence.append({ phase: 'source-authority-receipt', status: 'started' });
|
|
794
|
+
|
|
795
|
+
const receiptResult = verifySourceAuthorityReceipt({ plan, run: sourceRun });
|
|
796
|
+
if (!receiptResult.passed) {
|
|
797
|
+
await evidence.append({
|
|
798
|
+
phase: 'source-authority-receipt',
|
|
799
|
+
status: 'failed',
|
|
800
|
+
reason: receiptResult.reason,
|
|
801
|
+
});
|
|
802
|
+
throw new ReleaseError(
|
|
803
|
+
GATE_FAILED,
|
|
804
|
+
`source authority receipt verification failed: ${receiptResult.reason}`,
|
|
805
|
+
{ reason: receiptResult.reason },
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
await evidence.append({ phase: 'source-authority-receipt', status: 'passed' });
|
|
810
|
+
}
|
|
811
|
+
|
|
731
812
|
// All checkpoints must be succeeded or skipped (no failed/pending),
|
|
732
813
|
// except marketplace install checkpoints which are re-verified by verify
|
|
733
814
|
// itself via consumer verification (human attestation or automatic).
|
|
@@ -1002,7 +1083,10 @@ export async function verifyRelease(options) {
|
|
|
1002
1083
|
algorithmVersion: INSTALLATION_CONTRACT_ALGORITHM_VERSION,
|
|
1003
1084
|
});
|
|
1004
1085
|
|
|
1005
|
-
|
|
1086
|
+
// A current-run resource-closure receipt requires a fresh isolated
|
|
1087
|
+
// install. Installation-contract reuse alone cannot prove the
|
|
1088
|
+
// installed Skill resources are reachable.
|
|
1089
|
+
if (skipDecision === 'NOT_REQUIRED_UNCHANGED' && !plan.skillResourceClosure) {
|
|
1006
1090
|
adapterChecks.push({
|
|
1007
1091
|
actionId: action.id,
|
|
1008
1092
|
actionType: action.type,
|
|
@@ -1241,6 +1325,103 @@ export async function verifyRelease(options) {
|
|
|
1241
1325
|
);
|
|
1242
1326
|
}
|
|
1243
1327
|
|
|
1328
|
+
// =======================================================================
|
|
1329
|
+
// Step 4b: Skill resource closure check on consumer install surfaces
|
|
1330
|
+
// Re-run the closure check on each declared consumer install surface.
|
|
1331
|
+
// If the plan declares skillResourceClosure, every declared host surface
|
|
1332
|
+
// must be checked and pass; undeclared or failed surfaces block VERIFIED.
|
|
1333
|
+
// =======================================================================
|
|
1334
|
+
const skillResourceClosureReceipts = [
|
|
1335
|
+
...(smokeTest.skillResourceClosureReceipts ?? []),
|
|
1336
|
+
];
|
|
1337
|
+
if (plan.skillResourceClosure) {
|
|
1338
|
+
await evidence.append({ phase: 'verify', step: 'skill-resource-closure', status: 'started' });
|
|
1339
|
+
|
|
1340
|
+
// Collect install paths from marketplace adapter checks
|
|
1341
|
+
const installSurfaces = [];
|
|
1342
|
+
const actionDistribution = {
|
|
1343
|
+
'claude-marketplace-install': 'claude-plugin',
|
|
1344
|
+
'codex-marketplace-install': 'codex-plugin',
|
|
1345
|
+
'kimi-marketplace-install': 'kimi-plugin',
|
|
1346
|
+
'codebuddy-marketplace-install': 'codebuddy-plugin',
|
|
1347
|
+
};
|
|
1348
|
+
for (const check of adapterChecks) {
|
|
1349
|
+
const distribution = actionDistribution[check.actionType];
|
|
1350
|
+
if (distribution && check.observation?.installPath) {
|
|
1351
|
+
installSurfaces.push({
|
|
1352
|
+
host: distribution.replace('-plugin', ''),
|
|
1353
|
+
distribution,
|
|
1354
|
+
installPath: check.observation.installPath,
|
|
1355
|
+
actionId: check.actionId,
|
|
1356
|
+
unitId: actions.find((a) => a.id === check.actionId)?.unitId,
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
for (const surface of installSurfaces) {
|
|
1362
|
+
const closureResult = await checkSkillResourceClosure({
|
|
1363
|
+
snapshotDir: surface.installPath,
|
|
1364
|
+
host: surface.host,
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
if (closureResult.findings.length > 0) {
|
|
1368
|
+
await evidence.append({
|
|
1369
|
+
phase: 'verify',
|
|
1370
|
+
step: 'skill-resource-closure',
|
|
1371
|
+
status: 'failed',
|
|
1372
|
+
host: surface.host,
|
|
1373
|
+
findingCount: closureResult.findings.length,
|
|
1374
|
+
findings: closureResult.findings,
|
|
1375
|
+
});
|
|
1376
|
+
throw new ReleaseError(
|
|
1377
|
+
POST_PUBLISH_VERIFY_FAILED,
|
|
1378
|
+
`skill resource closure check failed for ${surface.host} consumer install: ${closureResult.findings.length} finding(s)`,
|
|
1379
|
+
{ host: surface.host, findings: closureResult.findings },
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
const expectedUnitReceipt = plan.skillResourceClosure.unitReceipts
|
|
1383
|
+
.find((item) => item.unitId === surface.unitId);
|
|
1384
|
+
if (!expectedUnitReceipt || closureResult.skillCount === 0) {
|
|
1385
|
+
throw new ReleaseError(
|
|
1386
|
+
POST_PUBLISH_VERIFY_FAILED,
|
|
1387
|
+
`skill resource closure installed surface is empty or unbound for ${surface.distribution}`,
|
|
1388
|
+
{ unitId: surface.unitId, distribution: surface.distribution },
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
skillResourceClosureReceipts.push(createSkillResourceClosureReceipt(
|
|
1392
|
+
closureResult,
|
|
1393
|
+
{
|
|
1394
|
+
host: surface.host,
|
|
1395
|
+
distribution: surface.distribution,
|
|
1396
|
+
actionId: surface.actionId,
|
|
1397
|
+
unitId: surface.unitId,
|
|
1398
|
+
checkedAt: clockFn(),
|
|
1399
|
+
exitCode: 0,
|
|
1400
|
+
},
|
|
1401
|
+
));
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
const receiptCoverage = evaluateConsumerSkillResourceClosureReceipts(
|
|
1405
|
+
plan,
|
|
1406
|
+
skillResourceClosureReceipts,
|
|
1407
|
+
);
|
|
1408
|
+
if (!receiptCoverage.passed) {
|
|
1409
|
+
throw new ReleaseError(
|
|
1410
|
+
POST_PUBLISH_VERIFY_FAILED,
|
|
1411
|
+
'skill resource closure receipt set does not match declared consumer distributions',
|
|
1412
|
+
receiptCoverage,
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
await evidence.append({
|
|
1417
|
+
phase: 'verify',
|
|
1418
|
+
step: 'skill-resource-closure',
|
|
1419
|
+
status: 'completed',
|
|
1420
|
+
surfaceCount: installSurfaces.length,
|
|
1421
|
+
receipts: skillResourceClosureReceipts,
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1244
1425
|
// =======================================================================
|
|
1245
1426
|
// All verifications passed — write verify run
|
|
1246
1427
|
// =======================================================================
|
|
@@ -1285,6 +1466,7 @@ export async function verifyRelease(options) {
|
|
|
1285
1466
|
}),
|
|
1286
1467
|
gateResults: consumerGateResults,
|
|
1287
1468
|
consumerVerificationReceipts,
|
|
1469
|
+
skillResourceClosureReceipts,
|
|
1288
1470
|
startedAt: clockFn(),
|
|
1289
1471
|
finishedAt: clockFn(),
|
|
1290
1472
|
};
|
package/src/core/errors.mjs
CHANGED
|
@@ -88,6 +88,12 @@ const EXIT_CODE_MAP = Object.freeze({
|
|
|
88
88
|
RELEASE_DOCS_REFRESH_STALE: 45,
|
|
89
89
|
RELEASE_DOCS_STALE: 46,
|
|
90
90
|
CONSUMER_VERIFICATION_DEFERRED: 47,
|
|
91
|
+
CONFIG_MISSING: 48,
|
|
92
|
+
REMOTE_UNAVAILABLE: 49,
|
|
93
|
+
REF_MISSING: 50,
|
|
94
|
+
NOT_DEFAULT: 51,
|
|
95
|
+
CONTENT_MISMATCH: 52,
|
|
96
|
+
DIRTY_SOURCE_INPUT: 53,
|
|
91
97
|
});
|
|
92
98
|
|
|
93
99
|
// ---- Error code constants ----
|
|
@@ -130,6 +136,12 @@ export const RELEASE_DOCS_CONFLICT = 'RELEASE_DOCS_CONFLICT';
|
|
|
130
136
|
export const RELEASE_DOCS_REFRESH_STALE = 'RELEASE_DOCS_REFRESH_STALE';
|
|
131
137
|
export const RELEASE_DOCS_STALE = 'RELEASE_DOCS_STALE';
|
|
132
138
|
export const CONSUMER_VERIFICATION_DEFERRED = 'CONSUMER_VERIFICATION_DEFERRED';
|
|
139
|
+
export const CONFIG_MISSING = 'CONFIG_MISSING';
|
|
140
|
+
export const REMOTE_UNAVAILABLE = 'REMOTE_UNAVAILABLE';
|
|
141
|
+
export const REF_MISSING = 'REF_MISSING';
|
|
142
|
+
export const NOT_DEFAULT = 'NOT_DEFAULT';
|
|
143
|
+
export const CONTENT_MISMATCH = 'CONTENT_MISMATCH';
|
|
144
|
+
export const DIRTY_SOURCE_INPUT = 'DIRTY_SOURCE_INPUT';
|
|
133
145
|
|
|
134
146
|
/**
|
|
135
147
|
* Typed error for release-skill operations.
|
package/src/core/plan.mjs
CHANGED
|
@@ -165,6 +165,35 @@ export function validatePlan(plan) {
|
|
|
165
165
|
);
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
+
|
|
169
|
+
if (plan.skillResourceClosure) {
|
|
170
|
+
const receipts = plan.skillResourceClosure.unitReceipts ?? [];
|
|
171
|
+
const receiptUnitIds = receipts.map((item) => item.unitId);
|
|
172
|
+
if (
|
|
173
|
+
new Set(receiptUnitIds).size !== receiptUnitIds.length
|
|
174
|
+
|| JSON.stringify([...receiptUnitIds].sort()) !== JSON.stringify([...unitsById.keys()].sort())
|
|
175
|
+
) {
|
|
176
|
+
throw new ReleaseError(
|
|
177
|
+
GATE_FAILED,
|
|
178
|
+
'skill resource closure receipts must cover every release unit exactly once',
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const totals = {
|
|
182
|
+
totalSkillCount: receipts.reduce((sum, item) => sum + item.skillCount, 0),
|
|
183
|
+
totalReferenceCount: receipts.reduce((sum, item) => sum + item.referenceCount, 0),
|
|
184
|
+
totalSourceOnlyCount: receipts.reduce((sum, item) => sum + item.sourceOnlyCount, 0),
|
|
185
|
+
totalFindingCount: receipts.reduce((sum, item) => sum + item.findingCount, 0),
|
|
186
|
+
};
|
|
187
|
+
for (const [field, expected] of Object.entries(totals)) {
|
|
188
|
+
if (plan.skillResourceClosure[field] !== expected) {
|
|
189
|
+
throw new ReleaseError(
|
|
190
|
+
GATE_FAILED,
|
|
191
|
+
`skill resource closure ${field} does not match unit receipts`,
|
|
192
|
+
{ expected, observed: plan.skillResourceClosure[field] },
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
168
197
|
}
|
|
169
198
|
|
|
170
199
|
/**
|