release-skill 0.9.10 → 0.9.11

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 (69) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +2 -2
  4. package/.kimi-plugin/plugin.json +1 -1
  5. package/CHANGELOG.md +24 -0
  6. package/INSTALL.md +2 -2
  7. package/INSTALL.zh-CN.md +2 -2
  8. package/README.md +22 -12
  9. package/README.zh-CN.md +21 -12
  10. package/adapters/claude/.claude-plugin/plugin.json +1 -1
  11. package/adapters/claude/bin/consumer-contract-vectors.json +7 -7
  12. package/adapters/claude/bin/error-codes.json +1 -1
  13. package/adapters/claude/bin/foundation-resource-binding.json +1 -1
  14. package/adapters/claude/bin/registry.json +8 -1
  15. package/adapters/claude/bin/release-skill.bundle.mjs +1018 -899
  16. package/adapters/claude/bin/rules.json +1 -1
  17. package/adapters/claude/data/hosts/codebuddy/host-descriptor.json +5 -0
  18. package/adapters/claude/schemas/release-project.schema.json +1 -1
  19. package/adapters/claude/src/schemas/engineering-baseline.schema.json +77 -0
  20. package/adapters/claude/src/schemas/host-descriptor.schema.json +9 -2
  21. package/adapters/codex/.codex-plugin/plugin.json +2 -2
  22. package/adapters/codex/bin/consumer-contract-vectors.json +7 -7
  23. package/adapters/codex/bin/error-codes.json +1 -1
  24. package/adapters/codex/bin/foundation-resource-binding.json +1 -1
  25. package/adapters/codex/bin/registry.json +8 -1
  26. package/adapters/codex/bin/release-skill.bundle.mjs +1018 -899
  27. package/adapters/codex/bin/rules.json +1 -1
  28. package/adapters/codex/data/hosts/codebuddy/host-descriptor.json +5 -0
  29. package/adapters/codex/schemas/release-project.schema.json +1 -1
  30. package/adapters/codex/src/schemas/engineering-baseline.schema.json +77 -0
  31. package/adapters/codex/src/schemas/host-descriptor.schema.json +9 -2
  32. package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
  33. package/adapters/kimi/bin/consumer-contract-vectors.json +7 -7
  34. package/adapters/kimi/bin/error-codes.json +1 -1
  35. package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
  36. package/adapters/kimi/bin/registry.json +8 -1
  37. package/adapters/kimi/bin/release-skill.bundle.mjs +1018 -899
  38. package/adapters/kimi/bin/rules.json +1 -1
  39. package/adapters/kimi/data/hosts/codebuddy/host-descriptor.json +5 -0
  40. package/adapters/kimi/schemas/release-project.schema.json +1 -1
  41. package/adapters/kimi/src/schemas/engineering-baseline.schema.json +77 -0
  42. package/adapters/kimi/src/schemas/host-descriptor.schema.json +9 -2
  43. package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
  44. package/adapters/workbuddy/bin/consumer-contract-vectors.json +7 -7
  45. package/adapters/workbuddy/bin/error-codes.json +1 -1
  46. package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
  47. package/adapters/workbuddy/bin/registry.json +8 -1
  48. package/adapters/workbuddy/bin/release-skill.bundle.mjs +1018 -899
  49. package/adapters/workbuddy/bin/rules.json +1 -1
  50. package/adapters/workbuddy/data/hosts/codebuddy/host-descriptor.json +5 -0
  51. package/adapters/workbuddy/schemas/release-project.schema.json +1 -1
  52. package/adapters/workbuddy/src/schemas/engineering-baseline.schema.json +77 -0
  53. package/adapters/workbuddy/src/schemas/host-descriptor.schema.json +9 -2
  54. package/bin/consumer-contract-vectors.json +7 -7
  55. package/bin/error-codes.json +1 -1
  56. package/bin/foundation-resource-binding.json +1 -1
  57. package/bin/registry.json +8 -1
  58. package/bin/release-skill.bundle.mjs +1018 -899
  59. package/bin/rules.json +1 -1
  60. package/data/hosts/codebuddy/host-descriptor.json +5 -0
  61. package/package.json +4 -4
  62. package/platform-manifest.json +2 -2
  63. package/references/02-project-config.md +7 -1
  64. package/schemas/release-project.schema.json +1 -1
  65. package/src/commands/verify.mjs +47 -9
  66. package/src/core/release-metadata.mjs +51 -3
  67. package/src/producers/foundation-resource-projection.mjs +3 -3
  68. package/src/schemas/engineering-baseline.schema.json +77 -0
  69. package/src/schemas/host-descriptor.schema.json +9 -2
package/bin/rules.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "skill-family.contracts.rules",
4
- "contractsVersion": "1.15.0",
4
+ "contractsVersion": "1.16.0",
5
5
  "budget": {
6
6
  "firstVersionMax": 20,
7
7
  "absoluteMax": 30,
@@ -5,6 +5,11 @@
5
5
  "displayName": "CodeBuddy",
6
6
  "support": "manual",
7
7
  "maturity": "manual",
8
+ "verification": {
9
+ "driverId": "codebuddy-print-v1",
10
+ "authStrategy": "existing-user-state",
11
+ "credentialMutation": "host-managed"
12
+ },
8
13
  "sourceAliases": [],
9
14
  "pathCategories": [],
10
15
  "probeCapabilities": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "release-skill",
3
- "version": "0.9.10",
3
+ "version": "0.9.11",
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.2.0",
80
80
  "npm-registry-fetch": "18.0.2",
81
- "skill-family-contracts": "0.16.0",
82
- "skill-family-engineering-kit": "0.16.0",
83
- "skill-family-harness-node": "0.16.0",
81
+ "skill-family-contracts": "0.17.0",
82
+ "skill-family-engineering-kit": "0.17.0",
83
+ "skill-family-harness-node": "0.17.0",
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.9.10",
5
+ "version": "0.9.11",
6
6
  "logicalRoot": "skills-src/",
7
7
  "logicalRootDigest": "aec96745b30289ed48d576fbfc98ecd3aacb834f83394be76d4734d7d0fc0ad4",
8
8
  "projectionDigest": "e5117d89c9f965e15a1464cc84f542cb1e75a685f842183d0cfc7a105ace5ceb",
9
- "projectionId": "release-skill:platform:0.9.10",
9
+ "projectionId": "release-skill:platform:0.9.11",
10
10
  "manifestRequired": true,
11
11
  "projectionSources": {
12
12
  "skills-src-root": {
@@ -42,7 +42,7 @@ releaseUnits: # 发布单元数组,至少 1 个
42
42
  version:
43
43
  source: <string> # 版本来源,如 "package.json"
44
44
  tagTemplate: <string> # tag 模板,如 "artifact-graph-v{version}"
45
- distributions: # 分发渠道数组
45
+ distributions: # 必填;分发渠道数组,也可显式设为 []
46
46
  - type: <string> # "npm" | "claude-plugin" | "codex-plugin"
47
47
  package: <string> # npm 包名(type 为 npm 时必填)
48
48
  # 插件渠道(claude-plugin/codex-plugin/kimi-plugin/codebuddy-plugin)
@@ -112,6 +112,12 @@ policy: # 可选,安全策略
112
112
  forbiddenContentPatterns: [<string>]
113
113
  ```
114
114
 
115
+ `distributions: []` 表示该发布单元不生成 npm 或插件市场动作,不表示跳过公开发布。
116
+ 生产模式的 `prepare` 仍会冻结 GitHub 快照,并生成 `push-snapshot`、`create-tag` 和
117
+ `github-release`。该单元的 `postPublish` 声明保持独立生效,其中 phase 为
118
+ `postVerify` 的 hook 也不受影响。因此纯插件可以先验证公开 GitHub Release,再向中央
119
+ Hub 投递登记提案。
120
+
115
121
  `publicSourceAuthorityReceipt` 只适用于 production prepare。`coordinatorUnitId`
116
122
  指定承载该文件的 GitHub Release;`subjectUnitIds` 必须引用现有 release unit,且每个
117
123
  unit 恰好声明一个 npm distribution。prepare 等所有 subject tarball 冻结后生成
@@ -155,7 +155,7 @@
155
155
  },
156
156
  "distributions": {
157
157
  "type": "array",
158
- "minItems": 1,
158
+ "description": "Distribution-specific publish and consumer checks. An explicit empty array publishes only the release unit's GitHub snapshot, tag, and GitHub Release; postPublish declarations, including postVerify-phase hooks, remain independent.",
159
159
  "items": {
160
160
  "type": "object",
161
161
  "required": [
@@ -64,6 +64,7 @@ import {
64
64
  isWorktreeFileClean,
65
65
  commitBaselineAdvance,
66
66
  } from '../core/baseline-advance.mjs';
67
+ import { updatePreviousPublicBaselines } from '../core/release-metadata.mjs';
67
68
  import { loadProjectConfig } from '../core/config.mjs';
68
69
  import { assertTransition, PUBLISHED, VERIFIED } from '../core/state-machine.mjs';
69
70
  import { clearFrozenMarker } from '../core/frozen-marker.mjs';
@@ -2330,17 +2331,41 @@ export async function verifyRelease(options) {
2330
2331
  // =======================================================================
2331
2332
  let baselineAdvance = null;
2332
2333
  const baselineAdvances = deriveBaselineAdvances(plan);
2333
- if (baselineAdvances.length > 0) {
2334
+ const hasFirstReleaseBaseline = (plan.units ?? [])
2335
+ .some((unit) => unit?.previousPublicBaseline?.mode === 'none');
2336
+ if (hasFirstReleaseBaseline || baselineAdvances.length > 0) {
2334
2337
  try {
2335
2338
  const configAbs = configPathOpt
2336
2339
  ? (isAbsolute(configPathOpt) ? configPathOpt : join(root, configPathOpt))
2337
2340
  : join(root, '.release-skill/project.yaml');
2338
2341
  const cleanBefore = await isWorktreeFileClean({ root, filePath: configAbs, execFn });
2339
- const applyResult = await applyBaselineAdvances({
2340
- configPath: configAbs,
2341
- advances: baselineAdvances,
2342
- validateFn: async () => { await loadProjectConfig({ root, configPath: configAbs }); },
2343
- });
2342
+ // A first-release plan must convert mode=none to a fully bound
2343
+ // baseline. The metadata updater processes the whole plan, so mixed
2344
+ // none+bound plans take this branch exclusively and do not omit or
2345
+ // double-write their already-bound units. Pure bound plans retain
2346
+ // the existing derive/apply path unchanged.
2347
+ let applyResult;
2348
+ if (hasFirstReleaseBaseline) {
2349
+ await loadProjectConfig({ root, configPath: configAbs });
2350
+ const metadataResult = await updatePreviousPublicBaselines({
2351
+ root,
2352
+ planPath,
2353
+ configPath: configAbs,
2354
+ validateFn: async () => { await loadProjectConfig({ root, configPath: configAbs }); },
2355
+ });
2356
+ applyResult = {
2357
+ changed: metadataResult.status === 'UPDATED',
2358
+ updatedUnits: metadataResult.status === 'UPDATED'
2359
+ ? metadataResult.units.map((unit) => unit.id)
2360
+ : [],
2361
+ };
2362
+ } else {
2363
+ applyResult = await applyBaselineAdvances({
2364
+ configPath: configAbs,
2365
+ advances: baselineAdvances,
2366
+ validateFn: async () => { await loadProjectConfig({ root, configPath: configAbs }); },
2367
+ });
2368
+ }
2344
2369
  baselineAdvance = { ...applyResult, committed: false };
2345
2370
  if (applyResult.changed && cleanBefore) {
2346
2371
  const versionLabel = (plan.units ?? [])
@@ -2362,12 +2387,25 @@ export async function verifyRelease(options) {
2362
2387
  committed: baselineAdvance.committed,
2363
2388
  });
2364
2389
  } catch (err) {
2365
- baselineAdvance = { failed: true, error: err.message };
2390
+ const errorDetails = err instanceof ReleaseError
2391
+ ? err.toJSON().details
2392
+ : undefined;
2393
+ baselineAdvance = {
2394
+ failed: true,
2395
+ error: err.message,
2396
+ ...(errorDetails ? { details: errorDetails } : {}),
2397
+ };
2366
2398
  await evidence.append({
2367
2399
  phase: 'verify',
2368
- step: 'baseline-advance',
2369
2400
  status: 'failed',
2370
- error: { code: err.code, message: err.message },
2401
+ error: {
2402
+ code: err.code,
2403
+ message: err.message,
2404
+ },
2405
+ details: {
2406
+ step: 'baseline-advance',
2407
+ ...(errorDetails ? { errorDetails } : {}),
2408
+ },
2371
2409
  });
2372
2410
  }
2373
2411
  }
@@ -1,5 +1,5 @@
1
1
  import { lstat, readFile } from 'node:fs/promises';
2
- import { resolve } from 'node:path';
2
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
3
3
  import YAML from 'yaml';
4
4
  import { replaceFileAtomic } from 'skill-family-harness-node';
5
5
 
@@ -20,7 +20,20 @@ function assertOid(value, label) {
20
20
  export async function updatePreviousPublicBaselines(options = {}) {
21
21
  const root = resolve(options.root ?? process.cwd());
22
22
  const planPath = resolve(options.planPath ?? '');
23
- const configPath = resolve(root, '.release-skill', 'project.yaml');
23
+ const configPath = isAbsolute(options.configPath ?? '')
24
+ ? resolve(options.configPath)
25
+ : resolve(root, options.configPath ?? '.release-skill/project.yaml');
26
+ const configRelativePath = relative(root, configPath);
27
+ if (
28
+ configRelativePath === '..'
29
+ || configRelativePath.startsWith(`..${sep}`)
30
+ || isAbsolute(configRelativePath)
31
+ ) {
32
+ throw new ReleaseError(CONFIG_INVALID, 'project config must stay within the project root', {
33
+ root,
34
+ configPath,
35
+ });
36
+ }
24
37
  let plan;
25
38
  let configRaw;
26
39
  let configStat;
@@ -69,6 +82,7 @@ export async function updatePreviousPublicBaselines(options = {}) {
69
82
  const old = configUnit.get('previousPublicBaseline', true)?.toJSON?.() ?? {};
70
83
  const next = {
71
84
  mode: 'bound',
85
+ ...(typeof old.githubHost === 'string' ? { githubHost: old.githubHost } : {}),
72
86
  repo: old.repo ?? unit.publicRepo,
73
87
  ref: old.ref ?? `refs/heads/${frozen.branch}`,
74
88
  commit: frozen.commit,
@@ -78,6 +92,15 @@ export async function updatePreviousPublicBaselines(options = {}) {
78
92
  if (!next.repo || !next.ref) {
79
93
  throw new ReleaseError(GATE_FAILED, `unit "${unit.id}" cannot derive its next public baseline identity`);
80
94
  }
95
+ const changed = [
96
+ 'mode',
97
+ 'repo',
98
+ 'ref',
99
+ 'commit',
100
+ 'tree',
101
+ 'manifestDigest',
102
+ ].some((field) => old[field] !== next[field]);
103
+ if (!changed) continue;
81
104
  configUnit.set('previousPublicBaseline', next);
82
105
  updates.push({
83
106
  id: unit.id,
@@ -86,16 +109,41 @@ export async function updatePreviousPublicBaselines(options = {}) {
86
109
  });
87
110
  }
88
111
 
112
+ if (updates.length === 0) {
113
+ return { status: 'UNCHANGED', configPath, units: [] };
114
+ }
89
115
  const nextRaw = doc.toString();
90
116
  if (nextRaw === configRaw) {
91
117
  return { status: 'UNCHANGED', configPath, units: updates };
92
118
  }
93
119
  try {
94
- await replaceFileAtomic(root, '.release-skill/project.yaml', nextRaw, {
120
+ await replaceFileAtomic(root, configRelativePath, nextRaw, {
95
121
  mode: configStat.mode & 0o777,
96
122
  });
97
123
  } catch (error) {
98
124
  throw new ReleaseError(CONFIG_INVALID, `cannot update project release metadata: ${error.message}`, error.details);
99
125
  }
126
+ if (typeof options.validateFn === 'function') {
127
+ try {
128
+ await options.validateFn(configPath);
129
+ } catch (error) {
130
+ try {
131
+ await replaceFileAtomic(root, configRelativePath, configRaw, {
132
+ mode: configStat.mode & 0o777,
133
+ });
134
+ } catch (restoreError) {
135
+ throw new ReleaseError(
136
+ CONFIG_INVALID,
137
+ `cannot restore project release metadata after validation failed: ${restoreError.message}`,
138
+ { validationError: error.message, restoreError: restoreError.details },
139
+ );
140
+ }
141
+ throw new ReleaseError(
142
+ GATE_FAILED,
143
+ `baseline advance reverted: rewritten config failed validation: ${error.message}`,
144
+ { configPath, cause: error.message },
145
+ );
146
+ }
147
+ }
100
148
  return { status: 'UPDATED', configPath, units: updates };
101
149
  }
@@ -55,9 +55,9 @@ import { digestBytes, publishFileOrReplace, readFileStrict, withTemporaryWorkspa
55
55
  import { canonicalJson } from '../core/digest.mjs';
56
56
 
57
57
  export const FOUNDATION_PACKAGES = Object.freeze({
58
- 'skill-family-contracts': '0.16.0',
59
- 'skill-family-engineering-kit': '0.16.0',
60
- 'skill-family-harness-node': '0.16.0',
58
+ 'skill-family-contracts': '0.17.0',
59
+ 'skill-family-engineering-kit': '0.17.0',
60
+ 'skill-family-harness-node': '0.17.0',
61
61
  });
62
62
 
63
63
  export const BINDING_RECORD_PATH = 'bin/foundation-resource-binding.json';
@@ -0,0 +1,77 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.skill-family.example/v1/engineering-baseline.json",
4
+ "title": "EngineeringBaseline",
5
+ "description": "Business-neutral identity for an engineering baseline. The contract carries the provider-owned baseline identity, canonical rule lineage references, and the Foundation reference implementation identity; it does not carry rule bodies, effects, equivalence, or exemption semantics.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "schemaVersion",
10
+ "kind",
11
+ "provider",
12
+ "id",
13
+ "version",
14
+ "digest",
15
+ "canonicalRuleRefs",
16
+ "referenceImplementation"
17
+ ],
18
+ "properties": {
19
+ "schemaVersion": { "const": 1 },
20
+ "kind": { "const": "skill-family.engineering-baseline" },
21
+ "provider": {
22
+ "type": "string",
23
+ "minLength": 1,
24
+ "description": "Provider that owns the baseline identity, for example an independent Audit implementation."
25
+ },
26
+ "id": {
27
+ "type": "string",
28
+ "minLength": 1,
29
+ "description": "Provider-scoped stable baseline identifier."
30
+ },
31
+ "version": {
32
+ "type": "string",
33
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
34
+ "description": "Semantic version of the provider-owned baseline identity."
35
+ },
36
+ "digest": {
37
+ "type": "string",
38
+ "pattern": "^[0-9a-f]{64}$",
39
+ "description": "SHA-256 digest of the canonical JSON of this document with digest omitted."
40
+ },
41
+ "canonicalRuleRefs": {
42
+ "type": "array",
43
+ "minItems": 1,
44
+ "items": { "$ref": "#/$defs/canonicalRuleRef" },
45
+ "description": "Sorted, unique references to provider-owned canonical rule lineages. Rule bodies and decisions remain outside this contract."
46
+ },
47
+ "referenceImplementation": {
48
+ "$ref": "#/$defs/referenceImplementation",
49
+ "description": "Identity of the Foundation reference implementation used by the baseline provider."
50
+ }
51
+ },
52
+ "$defs": {
53
+ "canonicalRuleRef": {
54
+ "type": "object",
55
+ "additionalProperties": false,
56
+ "required": ["id", "revision", "revisionDigest"],
57
+ "properties": {
58
+ "id": { "type": "string", "minLength": 1 },
59
+ "revision": { "type": "integer", "minimum": 1 },
60
+ "revisionDigest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
61
+ }
62
+ },
63
+ "referenceImplementation": {
64
+ "type": "object",
65
+ "additionalProperties": false,
66
+ "required": ["foundationVersion", "profile", "skeletonDigest"],
67
+ "properties": {
68
+ "foundationVersion": {
69
+ "type": "string",
70
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"
71
+ },
72
+ "profile": { "type": "string", "minLength": 1 },
73
+ "skeletonDigest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
74
+ }
75
+ }
76
+ }
77
+ }
@@ -25,7 +25,7 @@
25
25
  "additionalProperties": false,
26
26
  "required": ["driverId", "authStrategy", "credentialMutation"],
27
27
  "properties": {
28
- "driverId": { "enum": ["kimi-code-print-v1", "workbuddy-codebuddy-print-v1", "claude-code-print-v1", "codex-exec-v1", "qodercli-print-v1"] },
28
+ "driverId": { "enum": ["kimi-code-print-v1", "workbuddy-codebuddy-print-v1", "codebuddy-print-v1", "claude-code-print-v1", "codex-exec-v1", "qodercli-print-v1"] },
29
29
  "authStrategy": { "const": "existing-user-state" },
30
30
  "credentialMutation": { "const": "host-managed" }
31
31
  }
@@ -80,6 +80,13 @@
80
80
  "properties": { "verification": { "type": "object", "properties": { "driverId": { "const": "workbuddy-codebuddy-print-v1" } } } }
81
81
  }
82
82
  },
83
+ {
84
+ "if": { "properties": { "hostId": { "const": "codebuddy" } }, "required": ["hostId"] },
85
+ "then": {
86
+ "required": ["verification"],
87
+ "properties": { "verification": { "type": "object", "properties": { "driverId": { "const": "codebuddy-print-v1" } } } }
88
+ }
89
+ },
83
90
  {
84
91
  "if": { "properties": { "hostId": { "const": "claude" } }, "required": ["hostId"] },
85
92
  "then": {
@@ -102,7 +109,7 @@
102
109
  }
103
110
  },
104
111
  {
105
- "if": { "properties": { "hostId": { "enum": ["kimi-code", "workbuddy", "claude", "codex", "qoder"] } }, "required": ["hostId"] },
112
+ "if": { "properties": { "hostId": { "enum": ["kimi-code", "workbuddy", "codebuddy", "claude", "codex", "qoder"] } }, "required": ["hostId"] },
106
113
  "else": { "properties": { "verification": false } }
107
114
  },
108
115
  {