release-skill 0.4.2 → 0.5.0
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 +26 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +17 -6
- package/README.zh-CN.md +16 -5
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/error-codes.json +154 -0
- package/adapters/claude/bin/kernel-protocol.json +60 -0
- package/adapters/claude/bin/registry.json +165 -0
- package/adapters/claude/bin/release-skill.bundle.mjs +3838 -1445
- package/adapters/claude/bin/rules.json +104 -0
- package/adapters/claude/skills/release-assess/SKILL.md +2 -0
- package/adapters/claude/skills/release-help/SKILL.md +3 -1
- package/adapters/claude/skills/release-prepare/SKILL.md +2 -2
- package/adapters/claude/skills/release-verify/SKILL.md +2 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/error-codes.json +154 -0
- package/adapters/codex/bin/kernel-protocol.json +60 -0
- package/adapters/codex/bin/registry.json +165 -0
- package/adapters/codex/bin/release-skill.bundle.mjs +3838 -1445
- package/adapters/codex/bin/rules.json +104 -0
- package/adapters/codex/skills/release-assess/SKILL.md +2 -0
- package/adapters/codex/skills/release-help/SKILL.md +3 -1
- package/adapters/codex/skills/release-prepare/SKILL.md +2 -2
- package/adapters/codex/skills/release-verify/SKILL.md +2 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/error-codes.json +154 -0
- package/adapters/kimi/bin/kernel-protocol.json +60 -0
- package/adapters/kimi/bin/registry.json +165 -0
- package/adapters/kimi/bin/release-skill.bundle.mjs +3838 -1445
- package/adapters/kimi/bin/rules.json +104 -0
- package/adapters/kimi/skills/release-assess/SKILL.md +2 -0
- package/adapters/kimi/skills/release-help/SKILL.md +3 -1
- package/adapters/kimi/skills/release-prepare/SKILL.md +2 -2
- package/adapters/kimi/skills/release-verify/SKILL.md +2 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/error-codes.json +154 -0
- package/adapters/workbuddy/bin/kernel-protocol.json +60 -0
- package/adapters/workbuddy/bin/registry.json +165 -0
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +3838 -1445
- package/adapters/workbuddy/bin/rules.json +104 -0
- package/adapters/workbuddy/skills/release-assess/SKILL.md +2 -0
- package/adapters/workbuddy/skills/release-help/SKILL.md +3 -1
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +2 -2
- package/adapters/workbuddy/skills/release-verify/SKILL.md +2 -0
- package/bin/error-codes.json +154 -0
- package/bin/kernel-protocol.json +60 -0
- package/bin/registry.json +165 -0
- package/bin/release-skill.bundle.mjs +3838 -1445
- package/bin/rules.json +104 -0
- package/package.json +3 -1
- package/scripts/build-bundle.mjs +159 -5
- package/scripts/sync-public-files.mjs +5 -0
- package/skills/release-assess/SKILL.md +2 -0
- package/skills/release-help/SKILL.md +3 -1
- package/skills/release-prepare/SKILL.md +2 -2
- package/skills/release-verify/SKILL.md +2 -0
- package/skills-src/release-assess/SKILL.md +2 -0
- package/skills-src/release-help/SKILL.md +3 -1
- package/skills-src/release-prepare/SKILL.md +2 -2
- package/skills-src/release-verify/SKILL.md +2 -0
- package/src/adapters/contract.mjs +10 -10
- package/src/artifacts/entry.mjs +4 -1
- package/src/artifacts/producer-registry.mjs +9 -6
- package/src/artifacts/project-lock.mjs +324 -214
- package/src/commands/assess.mjs +60 -2
- package/src/commands/attest.mjs +11 -0
- package/src/core/approval.mjs +5 -2
- package/src/core/digest.mjs +47 -12
- package/src/core/foundation-inflight.mjs +39 -0
- package/src/core/hook-cache.mjs +3 -0
- package/src/core/installation-contract.mjs +4 -2
- package/src/core/plan.mjs +30 -27
- package/src/core/run.mjs +22 -36
- package/src/core/skill-resource-closure.mjs +6 -4
- package/src/core/trusted-resource.mjs +64 -15
- package/src/producers/build-adapters.mjs +27 -9
- package/src/producers/render-public-assets.mjs +12 -7
- package/src/producers/sync-skills.mjs +12 -7
- package/src/snapshot/public-map.mjs +10 -2
package/bin/rules.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"kind": "skill-family.contracts.rules",
|
|
4
|
+
"contractsVersion": "1.4.0",
|
|
5
|
+
"budget": {
|
|
6
|
+
"firstVersionMax": 20,
|
|
7
|
+
"absoluteMax": 30,
|
|
8
|
+
"note": "Mechanical gate: runChecks fails with SFC1008 when the mandatory rule count exceeds firstVersionMax, and fails harder when it exceeds absoluteMax. The count includes every rule with severity mandatory, including budget rules themselves."
|
|
9
|
+
},
|
|
10
|
+
"checkTypes": [
|
|
11
|
+
{
|
|
12
|
+
"type": "schema.compile",
|
|
13
|
+
"description": "Each registered schema must compile under its declared dialect; an uncompilable schema fails with SFC1012."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "schema.unique-id",
|
|
17
|
+
"description": "No two registered schemas may share a $id; duplicates fail with SFC1003."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "protocol.unique-name",
|
|
21
|
+
"description": "No two registered protocols may share the same name and version; duplicates fail with SFC1004."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"type": "schema.ref-resolves",
|
|
25
|
+
"description": "Every $ref in registered schemas must resolve within the registered schema set; unresolved targets fail with SFC1005."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"type": "schema.dialect-declared",
|
|
29
|
+
"description": "Every registered schema must declare a dialect from the supported set, consistent with its $schema URI; violations fail with SFC1006."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "fixture.positive-passes",
|
|
33
|
+
"description": "Every fixture expecting validity must validate against its target schema under its declared dialect and policy; mismatches fail with SFC1010."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"type": "fixture.negative-coded",
|
|
37
|
+
"description": "Every fixture expecting invalidity must fail with exactly its declared stable error code; mismatches fail with SFC1010."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "error-code.registered",
|
|
41
|
+
"description": "Every error code declared in fixture expectations must exist in the frozen error registry; unknown codes fail with SFC1009."
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "rules.budget",
|
|
45
|
+
"description": "The mandatory rule count must stay within the first-version budget (and never exceed the absolute cap); excess fails with SFC1008."
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"rules": [
|
|
49
|
+
{
|
|
50
|
+
"ruleId": "CR-001",
|
|
51
|
+
"severity": "mandatory",
|
|
52
|
+
"checkType": "schema.compile",
|
|
53
|
+
"target": { "registry": "schemas" }
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"ruleId": "CR-006",
|
|
57
|
+
"severity": "mandatory",
|
|
58
|
+
"checkType": "schema.unique-id",
|
|
59
|
+
"target": { "registry": "schemas" }
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"ruleId": "CR-007",
|
|
63
|
+
"severity": "mandatory",
|
|
64
|
+
"checkType": "protocol.unique-name",
|
|
65
|
+
"target": { "registry": "protocols" }
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"ruleId": "CR-008",
|
|
69
|
+
"severity": "mandatory",
|
|
70
|
+
"checkType": "schema.ref-resolves",
|
|
71
|
+
"target": { "registry": "schemas" }
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"ruleId": "CR-009",
|
|
75
|
+
"severity": "mandatory",
|
|
76
|
+
"checkType": "schema.dialect-declared",
|
|
77
|
+
"target": { "registry": "schemas" }
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"ruleId": "CR-010",
|
|
81
|
+
"severity": "mandatory",
|
|
82
|
+
"checkType": "fixture.positive-passes",
|
|
83
|
+
"target": { "fixtures": "expect-valid" }
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"ruleId": "CR-011",
|
|
87
|
+
"severity": "mandatory",
|
|
88
|
+
"checkType": "fixture.negative-coded",
|
|
89
|
+
"target": { "fixtures": "expect-invalid" }
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"ruleId": "CR-012",
|
|
93
|
+
"severity": "mandatory",
|
|
94
|
+
"checkType": "error-code.registered",
|
|
95
|
+
"target": { "references": "fixture-expectations" }
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"ruleId": "CR-013",
|
|
99
|
+
"severity": "mandatory",
|
|
100
|
+
"checkType": "rules.budget",
|
|
101
|
+
"target": { "budget": "firstVersionMax" }
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "release-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "广州市风荷科技有限公司"
|
|
@@ -76,6 +76,8 @@
|
|
|
76
76
|
"ajv-formats": "3.0.1",
|
|
77
77
|
"libnpmpublish": "11.1.0",
|
|
78
78
|
"npm-registry-fetch": "18.0.2",
|
|
79
|
+
"skill-family-contracts": "0.4.0",
|
|
80
|
+
"skill-family-harness-node": "0.4.0",
|
|
79
81
|
"yaml": "2.9.0"
|
|
80
82
|
},
|
|
81
83
|
"devDependencies": {
|
package/scripts/build-bundle.mjs
CHANGED
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* ESM file that runs without node_modules.
|
|
8
8
|
*
|
|
9
9
|
* Schemas and native addons remain external (loaded at runtime from
|
|
10
|
-
* the plugin root via PKG_ROOT).
|
|
10
|
+
* the plugin root via PKG_ROOT). Runtime JSON resources read by inlined
|
|
11
|
+
* dependency modules (new URL(<rel>, import.meta.url)) are emitted as
|
|
12
|
+
* deterministic sidecars next to the bundle (see "Runtime sidecar
|
|
13
|
+
* resources" section below).
|
|
11
14
|
*
|
|
12
15
|
* Usage:
|
|
13
16
|
* node scripts/build-bundle.mjs # build mode
|
|
@@ -15,7 +18,8 @@
|
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
20
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
18
|
-
import {
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
19
23
|
import { createHash } from 'node:crypto';
|
|
20
24
|
|
|
21
25
|
const CHECK_MODE = process.argv.includes('--check');
|
|
@@ -55,6 +59,100 @@ const REQUIRE_SHIM_PATTERN = /var __require = \/\* @__PURE__ \*\/ \(\(x\) => typ
|
|
|
55
59
|
|
|
56
60
|
const REQUIRE_SHIM_REPLACEMENT = `var __require = __bundleRealRequire;`;
|
|
57
61
|
|
|
62
|
+
// ─── Runtime sidecar resources for inlined dependencies ────────────────
|
|
63
|
+
//
|
|
64
|
+
// Dependencies bundled into the CLI (e.g. skill-family-contracts) can load
|
|
65
|
+
// JSON resources at module init via new URL(<rel>, import.meta.url). Once
|
|
66
|
+
// inlined into the bundle, import.meta.url is the bundle's own URL, so such
|
|
67
|
+
// reads resolve relative to the bundle directory (bin/). To keep the bundle
|
|
68
|
+
// self-contained in copied/installed closures (no node_modules), the build
|
|
69
|
+
// emits each referenced resource that is not already provided at that
|
|
70
|
+
// target as a deterministic sidecar next to the bundle. Reads whose target
|
|
71
|
+
// already exists (e.g. ../package.json resolving to the package's own
|
|
72
|
+
// package.json) are host-provided by design and are not emitted. --check
|
|
73
|
+
// verifies bundle bytes, every expected sidecar, and that every runtime
|
|
74
|
+
// read in the output resolves next to the bundle (fail-closed).
|
|
75
|
+
|
|
76
|
+
// Module markers esbuild leaves in the output: `// <path>` comments and
|
|
77
|
+
// `<path>() {` __esm keys, with <path> relative to the package root.
|
|
78
|
+
const MODULE_MARK_RE = /(?:\/\/\s*|")((?:\.\.\/)+[^\s"()]+\.(?:mjs|js|cjs))/g;
|
|
79
|
+
const RUNTIME_URL_READ_RE = /new URL\(\s*"([^"]+)"\s*,\s*import\.meta\.url\s*\)/g;
|
|
80
|
+
|
|
81
|
+
function isAbsoluteOrScheme(rel) {
|
|
82
|
+
return rel.startsWith('/') || /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(rel);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Collect every relative runtime read (module source → rel) present in the output. */
|
|
86
|
+
function collectRuntimeReads(content, pkgRoot) {
|
|
87
|
+
const reads = [];
|
|
88
|
+
const seenModules = new Set();
|
|
89
|
+
for (const mark of content.matchAll(MODULE_MARK_RE)) {
|
|
90
|
+
const modulePath = mark[1];
|
|
91
|
+
if (seenModules.has(modulePath)) continue;
|
|
92
|
+
seenModules.add(modulePath);
|
|
93
|
+
const sourceFile = join(pkgRoot, modulePath);
|
|
94
|
+
if (!existsSync(sourceFile)) continue;
|
|
95
|
+
const sourceText = readFileSync(sourceFile, 'utf-8');
|
|
96
|
+
for (const read of sourceText.matchAll(RUNTIME_URL_READ_RE)) {
|
|
97
|
+
const rel = read[1];
|
|
98
|
+
if (isAbsoluteOrScheme(rel)) continue;
|
|
99
|
+
reads.push({ modulePath, rel });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return reads;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Sidecars to emit: reads whose target next to the bundle is not already provided. */
|
|
106
|
+
function computeSidecars(reads, pkgRoot, outDir) {
|
|
107
|
+
const sidecars = [];
|
|
108
|
+
for (const { modulePath, rel } of reads) {
|
|
109
|
+
if (rel.endsWith('/')) continue; // directory URL composition; not a file load
|
|
110
|
+
const target = resolve(outDir, rel);
|
|
111
|
+
const source = resolve(join(pkgRoot, modulePath), '..', rel);
|
|
112
|
+
if (!existsSync(source)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`build-bundle: runtime read "${rel}" in ${modulePath} has no source resource (${source})`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
// Host-provided targets (e.g. ../package.json resolving to the package's
|
|
118
|
+
// own package.json) lie outside the bundle directory and must not be
|
|
119
|
+
// emitted or freshened. Targets inside the bundle directory are
|
|
120
|
+
// sidecar-managed: an existing target is NOT host-provided — a stale
|
|
121
|
+
// sidecar from a previous dependency generation must be freshened in
|
|
122
|
+
// write mode and must fail the --check byte comparison.
|
|
123
|
+
const relativeToOutDir = relative(outDir, target);
|
|
124
|
+
if (relativeToOutDir.startsWith('..') || isAbsolute(relativeToOutDir)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!sidecars.some((s) => s.target === target)) {
|
|
128
|
+
sidecars.push({ target, source });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return sidecars;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Fail-closed check: every relative runtime read in the output must resolve next to the bundle. */
|
|
135
|
+
function verifyRuntimeReadsResolve(content, outDir) {
|
|
136
|
+
const missing = new Set();
|
|
137
|
+
for (const read of content.matchAll(RUNTIME_URL_READ_RE)) {
|
|
138
|
+
const rel = read[1];
|
|
139
|
+
if (isAbsoluteOrScheme(rel) || rel.endsWith('/')) continue;
|
|
140
|
+
if (!existsSync(resolve(outDir, rel))) missing.add(rel);
|
|
141
|
+
}
|
|
142
|
+
return [...missing].sort();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Inlined Foundation modules must not perform bundle-relative runtime reads
|
|
146
|
+
// of files that adapter closures do not ship. skill-family-harness-node's
|
|
147
|
+
// report.mjs reads its own package.json at module init
|
|
148
|
+
// (readFileSync(new URL("../package.json", import.meta.url))); once inlined
|
|
149
|
+
// into the bundle that read resolves next to the bundle and breaks in
|
|
150
|
+
// adapter closures (which ship no package.json by design — the banner
|
|
151
|
+
// injects __bundlePkg instead). Neutralize the read at build time with the
|
|
152
|
+
// same injected package identity, fail-closed if Foundation changes shape.
|
|
153
|
+
const REPORT_PACKAGE_META_READ_RE =
|
|
154
|
+
/JSON\.parse\(readFileSync\(new URL\("\.\.\/package\.json", import\.meta\.url\), "utf8"\)\)/;
|
|
155
|
+
|
|
58
156
|
async function buildBundle() {
|
|
59
157
|
// Dynamic import so the script works even if esbuild is not yet installed.
|
|
60
158
|
let esbuild;
|
|
@@ -68,6 +166,25 @@ async function buildBundle() {
|
|
|
68
166
|
const pkgJson = JSON.parse(await readFile(join(PKG_ROOT, 'package.json'), 'utf-8'));
|
|
69
167
|
const banner = buildBanner({ name: pkgJson.name, version: pkgJson.version });
|
|
70
168
|
|
|
169
|
+
const identity = Object.freeze({ name: pkgJson.name, version: pkgJson.version });
|
|
170
|
+
|
|
171
|
+
const identityPlugins = [{
|
|
172
|
+
name: 'inline-foundation-package-identity',
|
|
173
|
+
setup(build) {
|
|
174
|
+
build.onLoad({ filter: /skill-family-harness-node[\\/]src[\\/]report\.mjs$/ }, async (args) => {
|
|
175
|
+
const source = await readFile(args.path, 'utf-8');
|
|
176
|
+
const injected = source.replace(REPORT_PACKAGE_META_READ_RE, () =>
|
|
177
|
+
`Object.freeze(${JSON.stringify(identity)})`);
|
|
178
|
+
if (injected === source) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
'build-bundle: could not neutralize Foundation report.mjs package.json read',
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return { contents: injected, loader: 'js' };
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
}];
|
|
187
|
+
|
|
71
188
|
const result = await esbuild.build({
|
|
72
189
|
entryPoints: [ENTRY],
|
|
73
190
|
absWorkingDir: PKG_ROOT,
|
|
@@ -77,6 +194,7 @@ async function buildBundle() {
|
|
|
77
194
|
target: 'node22',
|
|
78
195
|
outfile: OUTFILE,
|
|
79
196
|
banner: { js: banner },
|
|
197
|
+
plugins: identityPlugins,
|
|
80
198
|
// External: Node.js builtins, native addon loader, and the native addon itself.
|
|
81
199
|
external: [
|
|
82
200
|
'node:*',
|
|
@@ -106,11 +224,20 @@ async function buildBundle() {
|
|
|
106
224
|
// Normalize them so committed bundles pass repository whitespace checks.
|
|
107
225
|
content = content.replace(/[ \t]+$/gm, '');
|
|
108
226
|
|
|
109
|
-
|
|
227
|
+
// Compute runtime sidecars (resources read by inlined dependency modules
|
|
228
|
+
// via new URL(<rel>, import.meta.url), resolved next to the bundle).
|
|
229
|
+
const sidecars = computeSidecars(
|
|
230
|
+
collectRuntimeReads(content, PKG_ROOT),
|
|
231
|
+
PKG_ROOT,
|
|
232
|
+
dirname(OUTFILE),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return { content, sidecars };
|
|
110
236
|
}
|
|
111
237
|
|
|
112
238
|
async function main() {
|
|
113
|
-
const content = await buildBundle();
|
|
239
|
+
const { content, sidecars } = await buildBundle();
|
|
240
|
+
const outDir = dirname(OUTFILE);
|
|
114
241
|
|
|
115
242
|
if (CHECK_MODE) {
|
|
116
243
|
let existing;
|
|
@@ -128,11 +255,38 @@ async function main() {
|
|
|
128
255
|
console.error(` expected: sha256:${expectedHash}`);
|
|
129
256
|
process.exit(1);
|
|
130
257
|
}
|
|
258
|
+
for (const { target, source } of sidecars) {
|
|
259
|
+
let existingSidecar;
|
|
260
|
+
try {
|
|
261
|
+
existingSidecar = await readFile(target);
|
|
262
|
+
} catch {
|
|
263
|
+
console.error(`Bundle drift: runtime sidecar missing: ${relative(PKG_ROOT, target)}`);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
const expectedSidecar = await readFile(source);
|
|
267
|
+
if (!existingSidecar.equals(expectedSidecar)) {
|
|
268
|
+
console.error(`Bundle drift: runtime sidecar differs: ${relative(PKG_ROOT, target)}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const unresolved = verifyRuntimeReadsResolve(content, outDir);
|
|
273
|
+
if (unresolved.length > 0) {
|
|
274
|
+
console.error(`Bundle drift: runtime resource reads unresolved next to bundle: ${unresolved.join(', ')}`);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
131
277
|
console.log('Bundle in sync.');
|
|
132
278
|
process.exit(0);
|
|
133
279
|
}
|
|
134
280
|
|
|
135
|
-
|
|
281
|
+
for (const { target, source } of sidecars) {
|
|
282
|
+
const bytes = await readFile(source);
|
|
283
|
+
await mkdir(dirname(target), { recursive: true });
|
|
284
|
+
await writeFile(target, bytes);
|
|
285
|
+
const hash = createHash('sha256').update(bytes).digest('hex');
|
|
286
|
+
console.log(`Sidecar written: ${relative(PKG_ROOT, target)}`);
|
|
287
|
+
console.log(` sha256:${hash}`);
|
|
288
|
+
}
|
|
289
|
+
await mkdir(outDir, { recursive: true });
|
|
136
290
|
await writeFile(OUTFILE, content, 'utf-8');
|
|
137
291
|
const hash = createHash('sha256').update(content).digest('hex');
|
|
138
292
|
console.log(`Bundle written: ${OUTFILE}`);
|
|
@@ -228,6 +228,10 @@ async function collectEntries(packageRoot) {
|
|
|
228
228
|
// - release-notes/ (consumed by docs-refresh, not a public artifact)
|
|
229
229
|
// - native/*/build/ (native build artifacts, only prebuilds are shipped)
|
|
230
230
|
// - scripts/* (mostly dev-only; explicit whitelist below)
|
|
231
|
+
// - vendor/ (G4 foundation-pin: check-time drift evidence, not
|
|
232
|
+
// a distribution artifact; the shipped bundle
|
|
233
|
+
// inlines the adopted capability — matches the npm
|
|
234
|
+
// pack closure contract)
|
|
231
235
|
const baseFiles = allFiles.filter((f) => {
|
|
232
236
|
if (f.startsWith('adapters/')) return false;
|
|
233
237
|
if (f.startsWith('test/')) return false;
|
|
@@ -235,6 +239,7 @@ async function collectEntries(packageRoot) {
|
|
|
235
239
|
if (f.startsWith('release-notes/')) return false;
|
|
236
240
|
if (f.startsWith('native/') && f.includes('/build/')) return false;
|
|
237
241
|
if (f.startsWith('scripts/')) return false;
|
|
242
|
+
if (f.startsWith('vendor/')) return false;
|
|
238
243
|
return true;
|
|
239
244
|
});
|
|
240
245
|
|
|
@@ -44,6 +44,8 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" assess --root <path> --offlin
|
|
|
44
44
|
|
|
45
45
|
offline assess 不访问 GitHub/npm 认证,因此不会以顶层 `AUTH_MISSING` 作为正常诊断结果;生产认证缺口由 help 的 `readiness.productionPublish` 和发布前在线门禁报告。
|
|
46
46
|
|
|
47
|
+
在线模式下 npm 版本检查区分两种失败:registry 明确返回 E404/ETARGET 才算「版本不存在,无 gap」;网络、认证、超时等检查失败会以 `NPM_VERSION_CHECK_FAILED`(warning 级 gap)显式报告,不得把检查失败当作「版本未发布」。
|
|
48
|
+
|
|
47
49
|
重试时只保留最新结构化错误码和失败门,不沿用早期猜测。
|
|
48
50
|
|
|
49
51
|
## 后续引导
|
|
@@ -38,9 +38,11 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" assess --root <path> --offlin
|
|
|
38
38
|
# 日常发布快速路径:仅在发布前确认一次可读计划摘要,状态文件可恢复
|
|
39
39
|
node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" ship --root <path> --target-version <version> --json
|
|
40
40
|
# 开发阶段执行声明 hooks 并生成 prepare 可复用的内容绑定收据
|
|
41
|
+
# 配置时刻即授权(FM-16 处置 A):hook 是任意本地进程、无隔离、触发前无确认点,
|
|
41
42
|
# 命令调用本身即授权执行配置中的 hooks
|
|
42
43
|
node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" hooks validate --root <path> --json
|
|
43
44
|
# 仅旧冻结计划兼容:记录历史 Kimi/CodeBuddy 人工证明
|
|
45
|
+
# (本地自声明收据,证明力弱:--actor 仅非空字符串校验、无外部签名核验;新计划不适用)
|
|
44
46
|
node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" attest --root <path> \
|
|
45
47
|
--platform <kimi|codebuddy> --plugin <id> --result <passed|failed> --actor <person> --json
|
|
46
48
|
# 发布文档刷新:默认只读演练
|
|
@@ -59,7 +61,7 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" docs refresh --unit <id> \
|
|
|
59
61
|
- **只读演练**:`docs refresh --unit <id> --json` 输出逐文件相对路径、locale、新旧摘要、`version`、`locales`、`inputDigest`、`refreshDigest` 和 `nextCommand.argv`;候选无变化时 `status: "clean"`。
|
|
60
62
|
- **确认写入**:必须同时提供 `--write`、精确 `--confirm-refresh <refreshDigest>` 和 `--ack-local-document-write`,全部目标作为一个事务提交;成功后立即复演必须为 `clean`。
|
|
61
63
|
|
|
62
|
-
**授权边界**:本地发布文档写入授权只覆盖声明的本地文档目标,不是 Git 提交、push、publish 或安装的授权。hook/gate
|
|
64
|
+
**授权边界**:本地发布文档写入授权只覆盖声明的本地文档目标,不是 Git 提交、push、publish 或安装的授权。hook/gate 由对应命令调用直接授权——在 project.yaml 配置 hook 即完成授权(配置时刻即授权契约,FM-16 处置 A):hook 是任意本地进程、无文件系统/网络隔离、触发前无确认点,配置者须对其内容负责;恢复触发前强制确认门为后续加固项。写入后必须审阅、提交,再重新 prepare。
|
|
63
65
|
|
|
64
66
|
## 故障路由
|
|
65
67
|
|
|
@@ -11,9 +11,9 @@ description: Freeze an immutable release plan with local configuration, document
|
|
|
11
11
|
|
|
12
12
|
## 职责与边界
|
|
13
13
|
|
|
14
|
-
运行项目构建/测试 hook,生成公开快照并扫描泄漏,冻结不可变发布计划。prepare 自身不调用发布 adapter,但会执行用户配置的 hook。
|
|
14
|
+
运行项目构建/测试 hook,生成公开快照并扫描泄漏,冻结不可变发布计划。prepare 自身不调用发布 adapter,但会执行用户配置的 hook。
|
|
15
15
|
|
|
16
|
-
**Hook
|
|
16
|
+
**Hook 授权契约(配置时刻即授权,FM-16 处置 A)**: hook 是任意本地进程——在 `.release-skill/project.yaml` 中配置 hook 命令即完成授权,构成「配置时刻即授权」的显式契约。hook 不提供沙箱、无文件系统/网络隔离、触发前无确认点,命令调用本身即授权执行已配置的 hook 和 gate,不再设置额外人工授权环节;hook 可能产生项目目录外的副作用或远端写入,配置者须对其内容负责。恢复「触发前强制确认门」属于后续加固项(属设计变更,需随新版本引入),当前版本不提供该确认门。
|
|
17
17
|
|
|
18
18
|
**阶段通过规则**: 本阶段的通过只能由 CLI exit code 0 和结构化状态码 `PREPARED` 确认。Agent 无权自行宣布计划冻结成功。
|
|
19
19
|
|
|
@@ -19,6 +19,8 @@ verify 只接受 `PUBLISHED` 状态的源 run;`VERIFIED` 是终态,不会再
|
|
|
19
19
|
|
|
20
20
|
验证远端所有自动化 action 的实际状态与冻结计划一致。执行精确 `<package>@<version>` npm 安装到隔离目录,验证包名、版本、静态入口闭包,并在配置时验证 bin 路径安全和 CLI 烟雾输出。对 Claude/Codex marketplace distribution 执行全新隔离消费者安装验证。新计划中的 Kimi/CodeBuddy 安装只返回 `manualFollowUps`,明确标记 `verifiedBySystem: false`,不阻塞 `VERIFIED`;缺少该策略字段的旧冻结计划继续走历史 attestation 兼容路径。
|
|
21
21
|
|
|
22
|
+
**attestation 证明力边界**: `attest` 命令记录的旧计划兼容人工证明(`humanConfirmed: true` → `PASSED_MANUAL`)是本地自声明收据,不是签名见证:`--actor` 仅做非空字符串校验,无外部签名或身份核验,任何能运行 CLI 的进程都能自称任意 actor;伪造收据需写 `.release-skill` authority 目录,与直接改写收据文件属同一信任边界。因此旧兼容路径的人工证明只用于旧冻结计划收尾,不构成独立可审计的人工见证。新计划的 Kimi/CodeBuddy 安装一律走 `manualFollowUps`(`verifiedBySystem: false`),不参与 `VERIFIED` 终态判定。
|
|
23
|
+
|
|
22
24
|
**阶段通过规则**: 只有 CLI exit code 0 和结构化状态码 `VERIFIED` 才是完整终态。
|
|
23
25
|
|
|
24
26
|
**源 run 要求**: `--run` 必需;源 run 的所有 checkpoint 必须为 succeeded 或 skipped。
|
|
@@ -44,6 +44,8 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" assess --root <path> --offlin
|
|
|
44
44
|
|
|
45
45
|
offline assess 不访问 GitHub/npm 认证,因此不会以顶层 `AUTH_MISSING` 作为正常诊断结果;生产认证缺口由 help 的 `readiness.productionPublish` 和发布前在线门禁报告。
|
|
46
46
|
|
|
47
|
+
在线模式下 npm 版本检查区分两种失败:registry 明确返回 E404/ETARGET 才算「版本不存在,无 gap」;网络、认证、超时等检查失败会以 `NPM_VERSION_CHECK_FAILED`(warning 级 gap)显式报告,不得把检查失败当作「版本未发布」。
|
|
48
|
+
|
|
47
49
|
重试时只保留最新结构化错误码和失败门,不沿用早期猜测。
|
|
48
50
|
|
|
49
51
|
## 后续引导
|
|
@@ -38,9 +38,11 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" assess --root <path> --offlin
|
|
|
38
38
|
# 日常发布快速路径:仅在发布前确认一次可读计划摘要,状态文件可恢复
|
|
39
39
|
node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" ship --root <path> --target-version <version> --json
|
|
40
40
|
# 开发阶段执行声明 hooks 并生成 prepare 可复用的内容绑定收据
|
|
41
|
+
# 配置时刻即授权(FM-16 处置 A):hook 是任意本地进程、无隔离、触发前无确认点,
|
|
41
42
|
# 命令调用本身即授权执行配置中的 hooks
|
|
42
43
|
node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" hooks validate --root <path> --json
|
|
43
44
|
# 仅旧冻结计划兼容:记录历史 Kimi/CodeBuddy 人工证明
|
|
45
|
+
# (本地自声明收据,证明力弱:--actor 仅非空字符串校验、无外部签名核验;新计划不适用)
|
|
44
46
|
node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" attest --root <path> \
|
|
45
47
|
--platform <kimi|codebuddy> --plugin <id> --result <passed|failed> --actor <person> --json
|
|
46
48
|
# 发布文档刷新:默认只读演练
|
|
@@ -59,7 +61,7 @@ node "${CLAUDE_PLUGIN_ROOT}/bin/release-skill.mjs" docs refresh --unit <id> \
|
|
|
59
61
|
- **只读演练**:`docs refresh --unit <id> --json` 输出逐文件相对路径、locale、新旧摘要、`version`、`locales`、`inputDigest`、`refreshDigest` 和 `nextCommand.argv`;候选无变化时 `status: "clean"`。
|
|
60
62
|
- **确认写入**:必须同时提供 `--write`、精确 `--confirm-refresh <refreshDigest>` 和 `--ack-local-document-write`,全部目标作为一个事务提交;成功后立即复演必须为 `clean`。
|
|
61
63
|
|
|
62
|
-
**授权边界**:本地发布文档写入授权只覆盖声明的本地文档目标,不是 Git 提交、push、publish 或安装的授权。hook/gate
|
|
64
|
+
**授权边界**:本地发布文档写入授权只覆盖声明的本地文档目标,不是 Git 提交、push、publish 或安装的授权。hook/gate 由对应命令调用直接授权——在 project.yaml 配置 hook 即完成授权(配置时刻即授权契约,FM-16 处置 A):hook 是任意本地进程、无文件系统/网络隔离、触发前无确认点,配置者须对其内容负责;恢复触发前强制确认门为后续加固项。写入后必须审阅、提交,再重新 prepare。
|
|
63
65
|
|
|
64
66
|
## 故障路由
|
|
65
67
|
|
|
@@ -11,9 +11,9 @@ description: Freeze an immutable release plan with local configuration, document
|
|
|
11
11
|
|
|
12
12
|
## 职责与边界
|
|
13
13
|
|
|
14
|
-
运行项目构建/测试 hook,生成公开快照并扫描泄漏,冻结不可变发布计划。prepare 自身不调用发布 adapter,但会执行用户配置的 hook。
|
|
14
|
+
运行项目构建/测试 hook,生成公开快照并扫描泄漏,冻结不可变发布计划。prepare 自身不调用发布 adapter,但会执行用户配置的 hook。
|
|
15
15
|
|
|
16
|
-
**Hook
|
|
16
|
+
**Hook 授权契约(配置时刻即授权,FM-16 处置 A)**: hook 是任意本地进程——在 `.release-skill/project.yaml` 中配置 hook 命令即完成授权,构成「配置时刻即授权」的显式契约。hook 不提供沙箱、无文件系统/网络隔离、触发前无确认点,命令调用本身即授权执行已配置的 hook 和 gate,不再设置额外人工授权环节;hook 可能产生项目目录外的副作用或远端写入,配置者须对其内容负责。恢复「触发前强制确认门」属于后续加固项(属设计变更,需随新版本引入),当前版本不提供该确认门。
|
|
17
17
|
|
|
18
18
|
**阶段通过规则**: 本阶段的通过只能由 CLI exit code 0 和结构化状态码 `PREPARED` 确认。Agent 无权自行宣布计划冻结成功。
|
|
19
19
|
|
|
@@ -19,6 +19,8 @@ verify 只接受 `PUBLISHED` 状态的源 run;`VERIFIED` 是终态,不会再
|
|
|
19
19
|
|
|
20
20
|
验证远端所有自动化 action 的实际状态与冻结计划一致。执行精确 `<package>@<version>` npm 安装到隔离目录,验证包名、版本、静态入口闭包,并在配置时验证 bin 路径安全和 CLI 烟雾输出。对 Claude/Codex marketplace distribution 执行全新隔离消费者安装验证。新计划中的 Kimi/CodeBuddy 安装只返回 `manualFollowUps`,明确标记 `verifiedBySystem: false`,不阻塞 `VERIFIED`;缺少该策略字段的旧冻结计划继续走历史 attestation 兼容路径。
|
|
21
21
|
|
|
22
|
+
**attestation 证明力边界**: `attest` 命令记录的旧计划兼容人工证明(`humanConfirmed: true` → `PASSED_MANUAL`)是本地自声明收据,不是签名见证:`--actor` 仅做非空字符串校验,无外部签名或身份核验,任何能运行 CLI 的进程都能自称任意 actor;伪造收据需写 `.release-skill` authority 目录,与直接改写收据文件属同一信任边界。因此旧兼容路径的人工证明只用于旧冻结计划收尾,不构成独立可审计的人工见证。新计划的 Kimi/CodeBuddy 安装一律走 `manualFollowUps`(`verifiedBySystem: false`),不参与 `VERIFIED` 终态判定。
|
|
23
|
+
|
|
22
24
|
**阶段通过规则**: 只有 CLI exit code 0 和结构化状态码 `VERIFIED` 才是完整终态。
|
|
23
25
|
|
|
24
26
|
**源 run 要求**: `--run` 必需;源 run 的所有 checkpoint 必须为 succeeded 或 skipped。
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* @module adapters/contract
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { dirname, basename } from 'node:path';
|
|
17
|
+
|
|
18
|
+
import { writeFileAtomic } from 'skill-family-harness-node';
|
|
17
19
|
|
|
18
20
|
import { ReleaseError, AUTH_MISSING } from '../core/errors.mjs';
|
|
19
21
|
|
|
@@ -193,18 +195,16 @@ export function resolveTimeoutMs(action) {
|
|
|
193
195
|
* create, rename into place). Crash-safe: a partial write never replaces an
|
|
194
196
|
* existing file.
|
|
195
197
|
*
|
|
196
|
-
*
|
|
198
|
+
* 机制已委托 Foundation writeFileAtomic(resolveContained 路径包含三层校验 +
|
|
199
|
+
* 独占临时文件 + fsync + rename + 目录 fsync);文件内容与 mode 保持本地语义。
|
|
200
|
+
*
|
|
201
|
+
* @param {string} filePath - Absolute path to write.
|
|
197
202
|
* @param {object} value
|
|
198
203
|
*/
|
|
199
204
|
export async function writeEvidenceAtomic(filePath, value) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
await rename(tempPath, filePath);
|
|
204
|
-
} catch (err) {
|
|
205
|
-
await rm(tempPath, { force: true }).catch(() => {});
|
|
206
|
-
throw err;
|
|
207
|
-
}
|
|
205
|
+
const root = dirname(filePath);
|
|
206
|
+
const relPath = basename(filePath);
|
|
207
|
+
await writeFileAtomic(root, relPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
package/src/artifacts/entry.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { join } from 'node:path';
|
|
|
16
16
|
import { promisify } from 'node:util';
|
|
17
17
|
import { execFile } from 'node:child_process';
|
|
18
18
|
import { canonicalJson, sha256Hex } from '../core/digest.mjs';
|
|
19
|
+
import { digestDocument } from 'skill-family-contracts';
|
|
19
20
|
import { ReleaseError, PATH_UNSAFE } from '../core/errors.mjs';
|
|
20
21
|
|
|
21
22
|
const execFileAsync = promisify(execFile);
|
|
@@ -151,10 +152,12 @@ async function enumerateTreeEntries(root, dirPath, relBase) {
|
|
|
151
152
|
* @returns {string} `sha256:<hex>` digest.
|
|
152
153
|
*/
|
|
153
154
|
export function digestEntryManifest(entries) {
|
|
155
|
+
// 纯 JSON 投影(path/type/mode/size/sha256 定字段),直接委托 Foundation
|
|
156
|
+
// digestDocument(= sha256(contracts canonicalJson),与迁移前字节一致)。
|
|
154
157
|
const canonical = entries.map(({ path, type, mode, sha256, size }) => ({
|
|
155
158
|
path, type, mode, size, sha256,
|
|
156
159
|
}));
|
|
157
|
-
return `sha256:${
|
|
160
|
+
return `sha256:${digestDocument(canonical)}`;
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
// ---------------------------------------------------------------------------
|
|
@@ -14,10 +14,10 @@ import { readFile, readdir, stat, mkdir, writeFile, rm } from 'node:fs/promises'
|
|
|
14
14
|
import { join } from 'node:path';
|
|
15
15
|
import { mkdtemp } from 'node:fs/promises';
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
|
-
import { createHash } from 'node:crypto';
|
|
18
17
|
import { readFileSync } from 'node:fs';
|
|
19
18
|
|
|
20
19
|
import { canonicalJson, sha256Hex } from '../core/digest.mjs';
|
|
20
|
+
import { digestBytes } from 'skill-family-harness-node';
|
|
21
21
|
import {
|
|
22
22
|
ReleaseError,
|
|
23
23
|
ARTIFACT_POLICY_INVALID,
|
|
@@ -155,11 +155,14 @@ export async function createBuiltInProducerRegistry() {
|
|
|
155
155
|
* @returns {string}
|
|
156
156
|
*/
|
|
157
157
|
export function computeImplDigest(sourceBytes) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
// 多段哈希(源字节 + node 版本 + 固定 locale/timezone)等价于对拼接字节
|
|
159
|
+
// 的 sha256,委托 Foundation digestBytes 实现,字节逐位一致。
|
|
160
|
+
const bytes = Buffer.concat([
|
|
161
|
+
...sourceBytes,
|
|
162
|
+
Buffer.from(`node:${process.version}`),
|
|
163
|
+
Buffer.from('locale:en-US timezone:UTC'),
|
|
164
|
+
]);
|
|
165
|
+
return `sha256:${digestBytes(bytes)}`;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
// ---------------------------------------------------------------------------
|