release-skill 0.7.2 → 0.7.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-skill",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
5
5
  "author": {
6
6
  "name": "广州市风荷科技有限公司"
@@ -78,9 +78,9 @@
78
78
  "ajv-formats": "3.0.1",
79
79
  "libnpmpublish": "11.1.0",
80
80
  "npm-registry-fetch": "18.0.2",
81
- "skill-family-contracts": "0.8.0",
82
- "skill-family-engineering-kit": "0.8.0",
83
- "skill-family-harness-node": "0.8.0",
81
+ "skill-family-contracts": "0.8.1",
82
+ "skill-family-engineering-kit": "0.8.1",
83
+ "skill-family-harness-node": "0.8.1",
84
84
  "yaml": "2.9.0"
85
85
  },
86
86
  "devDependencies": {
@@ -2,11 +2,11 @@
2
2
  "schemaVersion": "1.0.0",
3
3
  "platformId": "release-skill",
4
4
  "platformName": "release-skill Plugin Platform",
5
- "version": "0.7.2",
5
+ "version": "0.7.4",
6
6
  "logicalRoot": "skills-src/",
7
7
  "logicalRootDigest": "3767fc36f42ca561f5e839cea7b7e5bc16d08c47867b5bd6086f25efd0cb0f06",
8
8
  "projectionDigest": "419f7f9601015459956e10aae74f42240ab2ac248238ff7494f3e761643d1749",
9
- "projectionId": "release-skill:platform:0.7.2",
9
+ "projectionId": "release-skill:platform:0.7.4",
10
10
  "manifestRequired": true,
11
11
  "projectionSources": {
12
12
  "skills-src-root": {
@@ -150,17 +150,6 @@ function verifyRuntimeReadsResolve(content, outDir) {
150
150
  return [...missing].sort();
151
151
  }
152
152
 
153
- // Inlined Foundation modules must not perform bundle-relative runtime reads
154
- // of files that adapter closures do not ship. skill-family-harness-node's
155
- // report.mjs reads its own package.json at module init
156
- // (readFileSync(new URL("../package.json", import.meta.url))); once inlined
157
- // into the bundle that read resolves next to the bundle and breaks in
158
- // adapter closures (which ship no package.json by design — the banner
159
- // injects __bundlePkg instead). Neutralize the read at build time with the
160
- // same injected package identity, fail-closed if Foundation changes shape.
161
- const REPORT_PACKAGE_META_READ_RE =
162
- /JSON\.parse\(readFileSync\(new URL\("\.\.\/package\.json", import\.meta\.url\), "utf8"\)\)/;
163
-
164
153
  async function buildBundle() {
165
154
  // Dynamic import so the script works even if esbuild is not yet installed.
166
155
  let esbuild;
@@ -175,25 +164,6 @@ async function buildBundle() {
175
164
  const sourceDigest = await computeBundleSourceDigest(PKG_ROOT);
176
165
  const banner = buildBanner({ name: pkgJson.name, version: pkgJson.version }, sourceDigest);
177
166
 
178
- const identity = Object.freeze({ name: pkgJson.name, version: pkgJson.version });
179
-
180
- const identityPlugins = [{
181
- name: 'inline-foundation-package-identity',
182
- setup(build) {
183
- build.onLoad({ filter: /skill-family-harness-node[\\/]src[\\/]report\.mjs$/ }, async (args) => {
184
- const source = await readFile(args.path, 'utf-8');
185
- const injected = source.replace(REPORT_PACKAGE_META_READ_RE, () =>
186
- `Object.freeze(${JSON.stringify(identity)})`);
187
- if (injected === source) {
188
- throw new Error(
189
- 'build-bundle: could not neutralize Foundation report.mjs package.json read',
190
- );
191
- }
192
- return { contents: injected, loader: 'js' };
193
- });
194
- },
195
- }];
196
-
197
167
  const result = await esbuild.build({
198
168
  entryPoints: [ENTRY],
199
169
  absWorkingDir: PKG_ROOT,
@@ -203,7 +173,6 @@ async function buildBundle() {
203
173
  target: 'node22',
204
174
  outfile: OUTFILE,
205
175
  banner: { js: banner },
206
- plugins: identityPlugins,
207
176
  // External: Node.js builtins, native addon loader, and the native addon itself.
208
177
  external: [
209
178
  'node:*',
@@ -13,8 +13,9 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
13
13
  import { canonicalJson, digestDocument } from 'skill-family-contracts';
14
14
  import { GATE_FAILED, ReleaseError } from './errors.mjs';
15
15
  import { computeFrozenSnapshot } from '../snapshot/frozen.mjs';
16
+ import { resolveSkillProjectionSurfaceHost } from '../platforms/registry.mjs';
16
17
 
17
- export const CHECKER_VERSION = 'skill-resource-closure-v2';
18
+ export const CHECKER_VERSION = 'skill-resource-closure-v3';
18
19
 
19
20
  const BARE_PREFIXES = ['references/', 'assets/', 'schemas/', 'examples/', 'scripts/'];
20
21
  const EXPLICIT_PREFIXES = [
@@ -32,6 +33,7 @@ const MACHINE_ABSOLUTE_PATTERN = /^(?:\/(?:Users|home|root|tmp|var|etc|opt)(?:\/
32
33
  // isPathToken, so prose words are never matched; fail-closed over silent miss.
33
34
  const HOME_DIRECTORY_PATTERN = /^(?:~|\$HOME|\$\{HOME\})\/.+/u;
34
35
  const ADAPTER_SURFACE_PATTERN = /^adapters\/([^/]+)$/u;
36
+ const PLATFORM_SURFACE_PATTERN = /^platforms\/(.+)$/u;
35
37
  // G3: skill-local resource closure directories. Regular files inside these
36
38
  // directories (under a skill directory) must be referenced by a SKILL.md of
37
39
  // the same surface or they are reported as stale on adapter surfaces.
@@ -58,6 +60,7 @@ export const CLASSIFICATION = Object.freeze({
58
60
  OUT_OF_BOUNDS: 'out_of_bounds',
59
61
  RESOURCE_DRIFT: 'resource_drift',
60
62
  STALE_RESOURCE: 'stale_resource',
63
+ UNKNOWN_PLATFORM_SURFACE: 'unknown_platform_surface',
61
64
  });
62
65
 
63
66
  export const FINDING_CODE = Object.freeze({
@@ -71,6 +74,7 @@ export const FINDING_CODE = Object.freeze({
71
74
  SYMLINK_NOT_ALLOWED: 'SYMLINK_NOT_ALLOWED',
72
75
  RESOURCE_DRIFT: 'RESOURCE_DRIFT',
73
76
  STALE_RESOURCE: 'STALE_RESOURCE',
77
+ UNKNOWN_PLATFORM_SURFACE: 'UNKNOWN_PLATFORM_SURFACE',
74
78
  });
75
79
 
76
80
  function toPosix(value) {
@@ -281,8 +285,14 @@ async function collectRegularFiles(dir, results) {
281
285
  }
282
286
 
283
287
  function inferSurfaceHost(surfaceId, defaultHost) {
284
- const match = ADAPTER_SURFACE_PATTERN.exec(surfaceId);
285
- return match?.[1] ?? defaultHost;
288
+ const adapterMatch = ADAPTER_SURFACE_PATTERN.exec(surfaceId);
289
+ if (adapterMatch) return adapterMatch[1];
290
+ const platformMatch = PLATFORM_SURFACE_PATTERN.exec(surfaceId);
291
+ if (platformMatch) {
292
+ return resolveSkillProjectionSurfaceHost(surfaceId)
293
+ ?? `unknown-platform:${platformMatch[1]}`;
294
+ }
295
+ return defaultHost;
286
296
  }
287
297
 
288
298
  function receiptProjection(result) {
@@ -413,6 +423,7 @@ export async function checkSkillResourceClosure({
413
423
  const pluginRoot = resolvePluginRoot(skill, scanRoot);
414
424
  const surfaceId = relativeStable(scanRoot, pluginRoot);
415
425
  const surfaceHost = inferSurfaceHost(surfaceId, host);
426
+ const firstSkillOnSurface = !surfaceMap.has(surfaceId);
416
427
  const surface = surfaceMap.get(surfaceId) ?? {
417
428
  id: surfaceId,
418
429
  host: surfaceHost,
@@ -422,6 +433,21 @@ export async function checkSkillResourceClosure({
422
433
  };
423
434
  surface.skillCount += 1;
424
435
  surfaceMap.set(surfaceId, surface);
436
+ if (firstSkillOnSurface
437
+ && PLATFORM_SURFACE_PATTERN.test(surfaceId)
438
+ && resolveSkillProjectionSurfaceHost(surfaceId) === null) {
439
+ findings.push({
440
+ host: surfaceHost,
441
+ surface: surfaceId,
442
+ skill: null,
443
+ line: null,
444
+ reference: surfaceId,
445
+ classification: CLASSIFICATION.UNKNOWN_PLATFORM_SURFACE,
446
+ resolutionRoot: surfaceId,
447
+ resolvedTarget: surfaceId,
448
+ code: FINDING_CODE.UNKNOWN_PLATFORM_SURFACE,
449
+ });
450
+ }
425
451
  if (!skillsBySurface.has(surfaceId)) skillsBySurface.set(surfaceId, []);
426
452
  skillsBySurface.get(surfaceId).push(skill);
427
453
 
@@ -134,6 +134,7 @@ function codexCrossValidateListEntry(found, action) {
134
134
 
135
135
  const CLAUDE = Object.freeze({
136
136
  id: 'claude',
137
+ skillProjectionSurface: 'platforms/claude-code',
137
138
  distributionType: 'claude-plugin',
138
139
  actionType: 'claude-marketplace-install',
139
140
  // Runtime adapter implementing this platform's marketplace-install action.
@@ -199,6 +200,7 @@ const CLAUDE = Object.freeze({
199
200
 
200
201
  const CODEX = Object.freeze({
201
202
  id: 'codex',
203
+ skillProjectionSurface: 'platforms/codex',
202
204
  distributionType: 'codex-plugin',
203
205
  actionType: 'codex-marketplace-install',
204
206
  adapter: 'plugin-marketplace',
@@ -260,6 +262,7 @@ const CODEX = Object.freeze({
260
262
 
261
263
  const KIMI = Object.freeze({
262
264
  id: 'kimi',
265
+ skillProjectionSurface: 'platforms/kimi-code',
263
266
  distributionType: 'kimi-plugin',
264
267
  actionType: 'kimi-marketplace-install',
265
268
  adapter: 'plugin-marketplace',
@@ -333,6 +336,7 @@ const KIMI = Object.freeze({
333
336
  // (distributionType/actionType/consumer) stays `codebuddy`.
334
337
  const CODEBUDDY = Object.freeze({
335
338
  id: 'codebuddy',
339
+ skillProjectionSurface: 'platforms/workbuddy',
336
340
  distributionType: 'codebuddy-plugin',
337
341
  actionType: 'codebuddy-marketplace-install',
338
342
  adapter: 'plugin-marketplace',
@@ -418,6 +422,7 @@ const VALID_REF_STRENGTHS = new Set(['commit-sha', 'name-ref', 'unfixable']);
418
422
  const VALID_OUTPUT_PROTOCOLS = new Set(['structured', 'text', 'none']);
419
423
  const VALID_IDENTITY_EVIDENCE = new Set(['list-record', 'install-output', 'filesystem-payload', 'human-attestation']);
420
424
  const VALID_DEGRADATION_POLICIES = new Set(['block', 'human-attestation', 'human-attestation-with-fallback']);
425
+ const SKILL_PROJECTION_SURFACE_PATTERN = /^platforms\/[a-z0-9]+(?:-[a-z0-9]+)*$/u;
421
426
 
422
427
  /**
423
428
  * Look up a platform descriptor by id.
@@ -431,6 +436,20 @@ export function getPlatform(id) {
431
436
  return platform;
432
437
  }
433
438
 
439
+ /**
440
+ * Resolve a declared skill projection surface to its public host name.
441
+ * The host is always derived from the descriptor's authoritative
442
+ * `buildAdapter.name`; unknown surfaces fail closed with `null`.
443
+ *
444
+ * @param {string} surfaceId
445
+ * @param {object[]} [registry]
446
+ * @returns {string|null}
447
+ */
448
+ export function resolveSkillProjectionSurfaceHost(surfaceId, registry = PLATFORMS) {
449
+ const platform = registry.find((item) => item.skillProjectionSurface === surfaceId);
450
+ return platform?.buildAdapter?.name ?? null;
451
+ }
452
+
434
453
  /**
435
454
  * Validate a registry (defaults to the module registry). Throws on the first
436
455
  * malformed description so a registry typo fails at import time.
@@ -439,6 +458,7 @@ export function getPlatform(id) {
439
458
  */
440
459
  export function assertRegistry(registry = PLATFORMS) {
441
460
  const seen = new Set();
461
+ const seenSkillProjectionSurfaces = new Set();
442
462
  for (const platform of registry) {
443
463
  const label = platform?.id ?? '<missing id>';
444
464
  if (typeof platform?.id !== 'string' || platform.id.length === 0) {
@@ -490,6 +510,17 @@ export function assertRegistry(registry = PLATFORMS) {
490
510
  if (typeof platform.buildAdapter.name !== 'string' || platform.buildAdapter.name.length === 0) {
491
511
  throw new Error(`platform registry: ${label} buildAdapter needs a non-empty name (its adapter directory name)`);
492
512
  }
513
+ if (typeof platform.skillProjectionSurface !== 'string'
514
+ || !SKILL_PROJECTION_SURFACE_PATTERN.test(platform.skillProjectionSurface)) {
515
+ throw new Error(`platform registry: ${label} skillProjectionSurface must match platforms/<slug>`);
516
+ }
517
+ if (seenSkillProjectionSurfaces.has(platform.skillProjectionSurface)) {
518
+ throw new Error(`platform registry: duplicate skillProjectionSurface "${platform.skillProjectionSurface}"; aliases must be unique`);
519
+ }
520
+ seenSkillProjectionSurfaces.add(platform.skillProjectionSurface);
521
+ if (resolveSkillProjectionSurfaceHost(platform.skillProjectionSurface, registry) !== platform.buildAdapter.name) {
522
+ throw new Error(`platform registry: ${label} skillProjectionSurface must map to buildAdapter.name`);
523
+ }
493
524
  if (!VALID_LIST_OUTPUTS.has(platform.jsonProtocol?.listOutput)) {
494
525
  throw new Error(`platform registry: ${label} has illegal jsonProtocol.listOutput "${platform.jsonProtocol?.listOutput}"`);
495
526
  }