release-skill 0.2.7 → 0.2.9

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 (36) 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 +36 -0
  7. package/INSTALL.md +4 -4
  8. package/INSTALL.zh-CN.md +4 -4
  9. package/README.md +7 -13
  10. package/README.zh-CN.md +7 -13
  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 +1153 -620
  14. package/adapters/claude/schemas/.render-manifest.json +2 -2
  15. package/adapters/claude/schemas/release-project.schema.json +60 -0
  16. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  17. package/adapters/codex/bin/release-skill.bundle.mjs +1153 -620
  18. package/adapters/codex/schemas/.render-manifest.json +2 -2
  19. package/adapters/codex/schemas/release-project.schema.json +60 -0
  20. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  21. package/adapters/kimi/bin/release-skill.bundle.mjs +1153 -620
  22. package/adapters/kimi/schemas/.render-manifest.json +2 -2
  23. package/adapters/kimi/schemas/release-project.schema.json +60 -0
  24. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  25. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1153 -620
  26. package/adapters/workbuddy/schemas/.render-manifest.json +2 -2
  27. package/adapters/workbuddy/schemas/release-project.schema.json +60 -0
  28. package/bin/release-skill-cli.mjs +4 -0
  29. package/bin/release-skill.bundle.mjs +1153 -620
  30. package/package.json +1 -1
  31. package/schemas/.render-manifest.json +2 -2
  32. package/schemas/release-project.schema.json +60 -0
  33. package/src/commands/assess.mjs +18 -5
  34. package/src/commands/prepare.mjs +59 -1
  35. package/src/core/config.mjs +48 -0
  36. package/src/core/public-surface.mjs +535 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-skill",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
5
5
  "author": {
6
6
  "name": "广州市风荷科技有限公司"
@@ -26,8 +26,8 @@
26
26
  "bytes": 35256
27
27
  },
28
28
  "release-project.schema.json": {
29
- "digest": "a3d46be1e1e8105103a505c6d58d14f3733c8da91ab8289e097ffd259bf1cd93",
30
- "bytes": 30982
29
+ "digest": "7ac18070ac41327a330ca35e32f466e3f2a8eb2b7df3a5755568e00f991ffdc1",
30
+ "bytes": 32497
31
31
  },
32
32
  "release-run.schema.json": {
33
33
  "digest": "bf3c8fb4ca2fc078272a45a6ee6bf663fd2420a722b2f995dbaabf2635e2179e",
@@ -455,6 +455,9 @@
455
455
  ]
456
456
  }
457
457
  },
458
+ "expectedPublicSurface": {
459
+ "$ref": "#/definitions/expectedPublicSurface"
460
+ },
458
461
  "publicFiles": {
459
462
  "type": "array",
460
463
  "minItems": 1,
@@ -761,6 +764,63 @@
761
764
  }
762
765
  },
763
766
  "definitions": {
767
+ "expectedPublicSurface": {
768
+ "type": "object",
769
+ "additionalProperties": false,
770
+ "required": [
771
+ "scanRoots"
772
+ ],
773
+ "properties": {
774
+ "scanRoots": {
775
+ "type": "array",
776
+ "minItems": 1,
777
+ "items": {
778
+ "$ref": "#/definitions/publicSurfaceScanRoot"
779
+ }
780
+ }
781
+ }
782
+ },
783
+ "publicSurfaceScanRoot": {
784
+ "type": "object",
785
+ "additionalProperties": false,
786
+ "required": [
787
+ "include",
788
+ "exclude"
789
+ ],
790
+ "properties": {
791
+ "sourceScope": {
792
+ "type": "string",
793
+ "enum": [
794
+ "unit",
795
+ "workspace"
796
+ ],
797
+ "default": "unit"
798
+ },
799
+ "root": {
800
+ "type": "string",
801
+ "minLength": 1,
802
+ "default": ".",
803
+ "pattern": "^(?:\\.$|(?!\\/)(?!\\.\\/)(?!.*\\\\)(?!.*:)(?!\\.\\.(?:\\/|$))(?!.*\\/\\.\\.\\/)(?!.*\\/\\.\\.$)(?!.*\\/\\.\\/)(?!.*\\/\\/)(?!.*\\0)(?!.*\\/\\.$)(?!.*\\/\\/$)(?!.*\\/$).*)$"
804
+ },
805
+ "include": {
806
+ "type": "array",
807
+ "minItems": 1,
808
+ "uniqueItems": true,
809
+ "items": {
810
+ "type": "string",
811
+ "minLength": 1
812
+ }
813
+ },
814
+ "exclude": {
815
+ "type": "array",
816
+ "uniqueItems": true,
817
+ "items": {
818
+ "type": "string",
819
+ "minLength": 1
820
+ }
821
+ }
822
+ }
823
+ },
764
824
  "hook": {
765
825
  "type": "object",
766
826
  "required": [
@@ -23,6 +23,7 @@ import { promisify } from 'node:util';
23
23
 
24
24
  import { loadProjectConfig } from '../core/config.mjs';
25
25
  import { ReleaseError, CONFIG_INVALID, GATE_FAILED } from '../core/errors.mjs';
26
+ import { collectExpectedPublicSurfaceAdoptionWarnings } from '../core/public-surface.mjs';
26
27
 
27
28
  const execFile = promisify(execFileCb);
28
29
 
@@ -880,23 +881,35 @@ export async function assessProject(options) {
880
881
  // --- 2. Topology identification ---
881
882
  const topology = identifyTopology(config);
882
883
 
883
- // --- 3. Common docs check ---
884
+ // --- 3. Expected public-surface adoption ---
885
+ for (const warning of collectExpectedPublicSurfaceAdoptionWarnings(config)) {
886
+ allGaps.push(createGap({
887
+ scope: GapScope.PROJECT,
888
+ category: GapCategory.POLICY,
889
+ severity: Severity.WARNING,
890
+ code: warning.code,
891
+ message: warning.message,
892
+ file: '.release-skill/project.yaml',
893
+ }));
894
+ }
895
+
896
+ // --- 4. Common docs check ---
884
897
  const docGaps = await checkCommonDocs(root, config);
885
898
  allGaps.push(...docGaps);
886
899
 
887
- // --- 4. Plugin manifest check ---
900
+ // --- 5. Plugin manifest check ---
888
901
  const manifestGaps = await checkPluginManifests(root, config);
889
902
  allGaps.push(...manifestGaps);
890
903
 
891
- // --- 5. Package metadata check ---
904
+ // --- 6. Package metadata check ---
892
905
  const metadataGaps = await checkPackageMetadata(root, config);
893
906
  allGaps.push(...metadataGaps);
894
907
 
895
- // --- 6. Remote prerequisites ---
908
+ // --- 7. Remote prerequisites ---
896
909
  const remoteGaps = await checkRemotePrerequisites(root, config, offline);
897
910
  allGaps.push(...remoteGaps);
898
911
 
899
- // --- 7. README structure check ---
912
+ // --- 8. README structure check ---
900
913
  const readmeGaps = await checkReadmeStructure(root, config);
901
914
  allGaps.push(...readmeGaps);
902
915
 
@@ -29,6 +29,10 @@ import { loadProjectConfig } from '../core/config.mjs';
29
29
  import { captureBaseline } from '../core/baseline.mjs';
30
30
  import { runHook } from '../core/hooks.mjs';
31
31
  import { computeHookCacheKey, readHookCache, writeHookCache } from '../core/hook-cache.mjs';
32
+ import {
33
+ assertExpectedPublicSurface,
34
+ collectExpectedPublicSurfaceAdoptionWarnings,
35
+ } from '../core/public-surface.mjs';
32
36
  import { runSnapshotVerificationGates } from '../core/verification-gates.mjs';
33
37
  import { createEvidenceWriter } from '../core/evidence.mjs';
34
38
  import { computePlanDigest, writePlanAtomic, writePlanImmutable } from '../core/plan.mjs';
@@ -1669,7 +1673,7 @@ export function buildExternalActions(unitResults, resolvedVersions, productionAs
1669
1673
  * every declared hook runs in full and the incremental hook cache is neither
1670
1674
  * read nor written.
1671
1675
  *
1672
- * @returns {Promise<{ planPath: string, planDigest: string, evidenceDir: string }>}
1676
+ * @returns {Promise<{ planPath: string, planDigest: string, evidenceDir: string, warnings: ReadonlyArray<object> }>}
1673
1677
  *
1674
1678
  * @throws {ReleaseError} on any gate failure. No PREPARED plan is written.
1675
1679
  */
@@ -1749,6 +1753,7 @@ export async function prepareRelease(options) {
1749
1753
  await evidence.append({ phase: 'config', status: 'started' });
1750
1754
 
1751
1755
  const { config, configPath, configDigest } = await loadProjectConfig({ root: realRoot });
1756
+ const adoptionWarnings = collectExpectedPublicSurfaceAdoptionWarnings(config);
1752
1757
 
1753
1758
  await evidence.append({
1754
1759
  phase: 'config',
@@ -1756,6 +1761,13 @@ export async function prepareRelease(options) {
1756
1761
  configPath: relative(realRoot, configPath),
1757
1762
  configDigest,
1758
1763
  });
1764
+ for (const warning of adoptionWarnings) {
1765
+ await evidence.append({
1766
+ phase: 'public-surface-adoption',
1767
+ status: 'warning',
1768
+ ...warning,
1769
+ });
1770
+ }
1759
1771
 
1760
1772
  // --- Step 1b: Resolve authoritative versions and gate release-document
1761
1773
  // freshness BEFORE hook authorization ---
@@ -1925,6 +1937,51 @@ export async function prepareRelease(options) {
1925
1937
  });
1926
1938
  }
1927
1939
 
1940
+ // --- Step 3b.1: Validate the post-build expected public surface ---
1941
+ // This gate runs after every declared hook (and the post-hook docs check)
1942
+ // but before source-authority closure, baseline, snapshots, remote reads,
1943
+ // and plan write. Therefore build-generated files must be explicitly
1944
+ // classified and every included source must already exist in publicFiles
1945
+ // before downstream authorities bind the release inputs.
1946
+ for (const unit of configUnits) {
1947
+ if (!unit?.expectedPublicSurface) continue;
1948
+ await evidence.append({
1949
+ phase: 'public-surface',
1950
+ status: 'started',
1951
+ unitId: unit.id,
1952
+ });
1953
+ try {
1954
+ const surface = await assertExpectedPublicSurface({
1955
+ root: realRoot,
1956
+ unit,
1957
+ });
1958
+ await evidence.append({
1959
+ phase: 'public-surface',
1960
+ status: 'completed',
1961
+ unitId: unit.id,
1962
+ ...surface.summary,
1963
+ });
1964
+ } catch (error) {
1965
+ await evidence.append({
1966
+ phase: 'public-surface',
1967
+ status: 'failed',
1968
+ unitId: unit.id,
1969
+ reason: error.details?.reason ?? 'PUBLIC_SURFACE_CHECK_FAILED',
1970
+ error: {
1971
+ code: error.code,
1972
+ message: error.message,
1973
+ },
1974
+ diagnostics: {
1975
+ missingMappings: error.details?.missingMappings ?? [],
1976
+ unexpectedMappings: error.details?.unexpectedMappings ?? [],
1977
+ unclassifiedFiles: error.details?.unclassifiedFiles ?? [],
1978
+ ambiguousFiles: error.details?.ambiguousFiles ?? [],
1979
+ },
1980
+ });
1981
+ throw error;
1982
+ }
1983
+ }
1984
+
1928
1985
  // --- Step 3c: Source authority content closure gate ---
1929
1986
  // After hooks complete, compute the deterministic source-input closure
1930
1987
  // and verify that closure inputs are clean (no staged/unstaged/untracked
@@ -2867,6 +2924,7 @@ export async function prepareRelease(options) {
2867
2924
  planPath: writtenPath,
2868
2925
  planDigest,
2869
2926
  evidenceDir,
2927
+ warnings: adoptionWarnings,
2870
2928
  };
2871
2929
  } catch (err) {
2872
2930
  // Record failure evidence
@@ -23,6 +23,7 @@ import Ajv from 'ajv';
23
23
  import addFormats from 'ajv-formats';
24
24
  import { canonicalJson, sha256Hex } from './digest.mjs';
25
25
  import { ReleaseError, CONFIG_INVALID } from './errors.mjs';
26
+ import { validatePublicSurfaceGlob } from './public-surface.mjs';
26
27
  import { canonicalPublicPath, publicPathCollisionKey } from '../snapshot/public-path.mjs';
27
28
  import { isReservedReleaseControlPath } from './baseline.mjs';
28
29
  import { readTrustedPackageResource } from './trusted-resource.mjs';
@@ -294,6 +295,53 @@ export async function loadProjectConfig({ root, configPath } = {}) {
294
295
  );
295
296
  }
296
297
 
298
+ // Validate expected public-surface roots and the deliberately small glob
299
+ // language before generic schema validation so diagnostics identify the
300
+ // exact unit, scan root, and pattern.
301
+ const surface = unit.expectedPublicSurface;
302
+ if (surface && typeof surface === 'object' && Array.isArray(surface.scanRoots)) {
303
+ for (const [rootIndex, scanRoot] of surface.scanRoots.entries()) {
304
+ if (!scanRoot || typeof scanRoot !== 'object') continue;
305
+ if (typeof scanRoot.root === 'string') {
306
+ try {
307
+ canonicalPublicPath(scanRoot.root, { allowDot: true });
308
+ } catch (err) {
309
+ throw new ReleaseError(
310
+ CONFIG_INVALID,
311
+ `unit "${unit.id}" has invalid expectedPublicSurface scan root: ${err.message}`,
312
+ {
313
+ unitId: unit.id,
314
+ rootIndex,
315
+ root: scanRoot.root,
316
+ field: 'expectedPublicSurface.scanRoots[].root',
317
+ },
318
+ );
319
+ }
320
+ }
321
+ for (const field of ['include', 'exclude']) {
322
+ if (!Array.isArray(scanRoot[field])) continue;
323
+ for (const pattern of scanRoot[field]) {
324
+ if (typeof pattern !== 'string') continue;
325
+ try {
326
+ validatePublicSurfaceGlob(pattern);
327
+ } catch (err) {
328
+ throw new ReleaseError(
329
+ CONFIG_INVALID,
330
+ `unit "${unit.id}" has invalid expectedPublicSurface ${field} glob: ${err.message}`,
331
+ {
332
+ unitId: unit.id,
333
+ rootIndex,
334
+ pattern,
335
+ field: `expectedPublicSurface.scanRoots[].${field}`,
336
+ reason: err.details?.reason ?? 'PUBLIC_SURFACE_GLOB_INVALID',
337
+ },
338
+ );
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+
297
345
  // Validate publicFiles[].from and .to
298
346
  if (Array.isArray(unit.publicFiles)) {
299
347
  for (const mapping of unit.publicFiles) {