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.
Files changed (57) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codebuddy-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +2 -2
  5. package/.kimi-plugin/plugin.json +1 -1
  6. package/CHANGELOG.md +45 -0
  7. package/INSTALL.md +32 -8
  8. package/INSTALL.zh-CN.md +28 -6
  9. package/README.md +29 -15
  10. package/README.zh-CN.md +27 -15
  11. package/adapters/claude/.claude-plugin/marketplace.json +1 -1
  12. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  13. package/adapters/claude/bin/release-skill.bundle.mjs +2202 -708
  14. package/adapters/claude/schemas/.render-manifest.json +6 -6
  15. package/adapters/claude/schemas/release-plan.schema.json +158 -0
  16. package/adapters/claude/schemas/release-project.schema.json +6 -0
  17. package/adapters/claude/schemas/release-run.schema.json +150 -0
  18. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  19. package/adapters/codex/bin/release-skill.bundle.mjs +2202 -708
  20. package/adapters/codex/schemas/.render-manifest.json +6 -6
  21. package/adapters/codex/schemas/release-plan.schema.json +158 -0
  22. package/adapters/codex/schemas/release-project.schema.json +6 -0
  23. package/adapters/codex/schemas/release-run.schema.json +150 -0
  24. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  25. package/adapters/kimi/bin/release-skill.bundle.mjs +2202 -708
  26. package/adapters/kimi/schemas/.render-manifest.json +6 -6
  27. package/adapters/kimi/schemas/release-plan.schema.json +158 -0
  28. package/adapters/kimi/schemas/release-project.schema.json +6 -0
  29. package/adapters/kimi/schemas/release-run.schema.json +150 -0
  30. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  31. package/adapters/workbuddy/bin/release-skill.bundle.mjs +2202 -708
  32. package/adapters/workbuddy/schemas/.render-manifest.json +6 -6
  33. package/adapters/workbuddy/schemas/release-plan.schema.json +158 -0
  34. package/adapters/workbuddy/schemas/release-project.schema.json +6 -0
  35. package/adapters/workbuddy/schemas/release-run.schema.json +150 -0
  36. package/bin/release-skill.bundle.mjs +2202 -708
  37. package/package.json +1 -1
  38. package/references/.render-manifest.json +4 -4
  39. package/references/02-project-config.md +28 -2
  40. package/references/05-evidence-and-errors.md +6 -0
  41. package/schemas/.render-manifest.json +6 -6
  42. package/schemas/release-plan.schema.json +158 -0
  43. package/schemas/release-project.schema.json +6 -0
  44. package/schemas/release-run.schema.json +150 -0
  45. package/scripts/sync-public-files.mjs +20 -9
  46. package/src/adapters/plugin-marketplace.mjs +82 -3
  47. package/src/commands/prepare.mjs +232 -1
  48. package/src/commands/publish.mjs +134 -0
  49. package/src/commands/reconcile.mjs +19 -0
  50. package/src/commands/setup.mjs +26 -0
  51. package/src/commands/verify.mjs +183 -1
  52. package/src/core/errors.mjs +12 -0
  53. package/src/core/plan.mjs +29 -0
  54. package/src/core/skill-resource-closure.mjs +425 -0
  55. package/src/core/source-authority.mjs +547 -0
  56. package/src/platforms/codebuddy.mjs +16 -3
  57. package/src/platforms/kimi.mjs +36 -9
@@ -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
- if (skipDecision === 'NOT_REQUIRED_UNCHANGED') {
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
  };
@@ -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
  /**