release-skill 0.1.8 → 0.1.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +34 -0
- package/INSTALL.md +4 -4
- package/INSTALL.zh-CN.md +4 -4
- package/README.md +34 -22
- package/README.zh-CN.md +24 -18
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +66 -14
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +66 -14
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +66 -14
- package/bin/release-skill.bundle.mjs +66 -14
- package/package.json +1 -1
- package/src/adapters/plugin-marketplace.mjs +121 -21
- package/src/snapshot/frozen.mjs +4 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "release-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "广州市风荷科技有限公司"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"defaultPrompt": [
|
|
21
21
|
"Assess this project for release readiness.",
|
|
22
|
-
"Prepare a release plan for version 0.1.
|
|
22
|
+
"Prepare a release plan for version 0.1.9.",
|
|
23
23
|
"Help me understand the release workflow."
|
|
24
24
|
]
|
|
25
25
|
}
|
package/.kimi-plugin/plugin.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.1.9 locale=en baseline=sha256:409f940c03e2d6fd41d33659b246b3d4b622918b4ac5a3a711d83aa09740ef11 -->
|
|
4
|
+
## [0.1.9] - 2026-07-23
|
|
5
|
+
|
|
6
|
+
v0.1.9 fixes a structural marketplace install verification failure: consumer installs that declare a plugin `source` subdirectory (e.g. `./adapters/claude`) now bind the installed payload to the declared subtree of the sealed whole-unit snapshot, and Claude's root `.in_use` marker is exempted as consumer-owned transport metadata. The sealed snapshot digest, release plan schema, and prepare-side freezing are unchanged, so already-frozen plans reconcile without re-approval. The npm package name (`release-skill`), publishing identity (`publisher: mzdbxqh`), public repository (`ifoohoo/release-skill`), and corporate maintainer remain unchanged.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Marketplace payload binding for subdirectory source layouts**: consumer
|
|
11
|
+
marketplaces may declare a plugin `source` subdirectory such as
|
|
12
|
+
`./adapters/claude`; the consumer CLI then installs only that subtree while
|
|
13
|
+
the sealed authority is the whole unit snapshot. Install verification now
|
|
14
|
+
revalidates the sealed whole-snapshot digest, re-reads the declared source
|
|
15
|
+
from the marketplace manifest inside the digest-verified snapshot entries,
|
|
16
|
+
and binds the installed payload to that prefix-stripped subtree. Root
|
|
17
|
+
layouts and Kimi keep the whole-tree comparison. This resolves the
|
|
18
|
+
flow-architect v0.4.1 and v0.5.0 PARTIAL marketplace install failures;
|
|
19
|
+
already-frozen plans reconcile unchanged because the sealed digest, plan
|
|
20
|
+
schema, and prepare-side freezing are untouched.
|
|
21
|
+
- **Claude `.in_use` transport marker exemption**: the Claude CLI writes an
|
|
22
|
+
empty `.in_use` marker into the plugin install root; it is now exempted as
|
|
23
|
+
consumer-owned transport metadata alongside the existing Codex/Kimi `.git`
|
|
24
|
+
exemption, through a single shared exclusion helper that also backs the
|
|
25
|
+
observe-time diagnostic fallback. Exemptions remain root-only: nested
|
|
26
|
+
markers, extra payload, byte tampering, and sealed authority tampering
|
|
27
|
+
still fail closed.
|
|
28
|
+
- **Regression coverage**: new protocol-level fake-CLI tests cover the
|
|
29
|
+
subdirectory claude cycle with `.in_use` (including byte-tamper,
|
|
30
|
+
extra-file, missing-file, `.git`-not-exempt, and nested-marker negatives),
|
|
31
|
+
fail-closed manifest anomalies (duplicate plugin entry, wrong marketplace
|
|
32
|
+
name, empty source projection), root-layout claude with `.in_use`, and a
|
|
33
|
+
codex subdirectory variant.
|
|
34
|
+
<!-- release-skill:changelog:end version=0.1.9 locale=en -->
|
|
35
|
+
|
|
36
|
+
|
|
3
37
|
<!-- release-skill:changelog:start version=0.1.8 locale=en baseline=sha256:1e1a0af9d5807bceb7af1eae88b726f67e6811dba4db579fd625c07f3bdbca89 -->
|
|
4
38
|
## [0.1.8] - 2026-07-23
|
|
5
39
|
|
package/INSTALL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](INSTALL.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
<!-- release-skill:release-version: 0.1.
|
|
5
|
+
<!-- release-skill:release-version: 0.1.9 -->
|
|
6
6
|
## Prerequisites
|
|
7
7
|
|
|
8
8
|
- Node.js 22.0.0 or later
|
|
@@ -68,7 +68,7 @@ as a version-pinned **manual** install plus trusted observation/attestation:
|
|
|
68
68
|
default branch), confirm the trust prompt, then reload:
|
|
69
69
|
|
|
70
70
|
```
|
|
71
|
-
/plugins install https://github.com/ifoohoo/release-skill/releases/tag/release-skill-v0.1.
|
|
71
|
+
/plugins install https://github.com/ifoohoo/release-skill/releases/tag/release-skill-v0.1.9
|
|
72
72
|
/plugins reload
|
|
73
73
|
```
|
|
74
74
|
|
|
@@ -83,10 +83,10 @@ as a version-pinned **manual** install plus trusted observation/attestation:
|
|
|
83
83
|
{
|
|
84
84
|
"consumer": "kimi",
|
|
85
85
|
"plugin": "release-skill",
|
|
86
|
-
"version": "0.1.
|
|
86
|
+
"version": "0.1.9",
|
|
87
87
|
"entrySkill": "release-help",
|
|
88
88
|
"repo": "ifoohoo/release-skill",
|
|
89
|
-
"ref": "release-skill-v0.1.
|
|
89
|
+
"ref": "release-skill-v0.1.9",
|
|
90
90
|
"installPath": "<kimiCodeHome>/plugins/managed/release-skill",
|
|
91
91
|
"planDigest": "<64-hex frozen plan digest>",
|
|
92
92
|
"payloadDigest": "<64-hex frozen snapshot payload digest>",
|
package/INSTALL.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](INSTALL.md)
|
|
4
4
|
|
|
5
|
-
<!-- release-skill:release-version: 0.1.
|
|
5
|
+
<!-- release-skill:release-version: 0.1.9 -->
|
|
6
6
|
## 前置条件
|
|
7
7
|
|
|
8
8
|
- Node.js 22.0.0 或更高版本
|
|
@@ -62,7 +62,7 @@ release-skill 把 Kimi 安装建模为“版本钉死的手动安装 + 可信观
|
|
|
62
62
|
它会安装最新 release 或默认分支),确认信任提示后重新加载:
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
/plugins install https://github.com/ifoohoo/release-skill/releases/tag/release-skill-v0.1.
|
|
65
|
+
/plugins install https://github.com/ifoohoo/release-skill/releases/tag/release-skill-v0.1.9
|
|
66
66
|
/plugins reload
|
|
67
67
|
```
|
|
68
68
|
|
|
@@ -76,10 +76,10 @@ release-skill 把 Kimi 安装建模为“版本钉死的手动安装 + 可信观
|
|
|
76
76
|
{
|
|
77
77
|
"consumer": "kimi",
|
|
78
78
|
"plugin": "release-skill",
|
|
79
|
-
"version": "0.1.
|
|
79
|
+
"version": "0.1.9",
|
|
80
80
|
"entrySkill": "release-help",
|
|
81
81
|
"repo": "ifoohoo/release-skill",
|
|
82
|
-
"ref": "release-skill-v0.1.
|
|
82
|
+
"ref": "release-skill-v0.1.9",
|
|
83
83
|
"installPath": "<kimiCodeHome>/plugins/managed/release-skill",
|
|
84
84
|
"planDigest": "<64 位十六进制冻结计划摘要>",
|
|
85
85
|
"payloadDigest": "<64 位十六进制冻结快照载荷摘要>",
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](README.zh-CN.md) · Installation: [English](INSTALL.md) / [简体中文](INSTALL.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
<!-- release-skill:release-version: 0.1.
|
|
5
|
+
<!-- release-skill:release-version: 0.1.9 -->
|
|
6
6
|
Release preparation for Claude Code, Codex, and Kimi Code, with human-edited files kept intact.
|
|
7
7
|
|
|
8
8
|
release-skill helps a maintainer answer three questions: what will be released,
|
|
@@ -11,29 +11,41 @@ reviewed artifacts first and publishes those same artifacts later; it does not
|
|
|
11
11
|
regenerate a README or re-pack the live workspace at the last step.
|
|
12
12
|
|
|
13
13
|
<!-- release-skill:managed:start id=latest-release -->
|
|
14
|
-
**0.1.
|
|
15
|
-
|
|
16
|
-
v0.1.
|
|
17
|
-
|
|
18
|
-
**
|
|
19
|
-
|
|
20
|
-
- **
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
14
|
+
**0.1.9** (2026-07-23)
|
|
15
|
+
|
|
16
|
+
v0.1.9 fixes a structural marketplace install verification failure: consumer installs that declare a plugin `source` subdirectory (e.g. `./adapters/claude`) now bind the installed payload to the declared subtree of the sealed whole-unit snapshot, and Claude's root `.in_use` marker is exempted as consumer-owned transport metadata. The sealed snapshot digest, release plan schema, and prepare-side freezing are unchanged, so already-frozen plans reconcile without re-approval. The npm package name (`release-skill`), publishing identity (`publisher: mzdbxqh`), public repository (`ifoohoo/release-skill`), and corporate maintainer remain unchanged.
|
|
17
|
+
|
|
18
|
+
**Fixed**
|
|
19
|
+
|
|
20
|
+
- **Marketplace payload binding for subdirectory source layouts**: consumer
|
|
21
|
+
marketplaces may declare a plugin `source` subdirectory such as
|
|
22
|
+
`./adapters/claude`; the consumer CLI then installs only that subtree while
|
|
23
|
+
the sealed authority is the whole unit snapshot. Install verification now
|
|
24
|
+
revalidates the sealed whole-snapshot digest, re-reads the declared source
|
|
25
|
+
from the marketplace manifest inside the digest-verified snapshot entries,
|
|
26
|
+
and binds the installed payload to that prefix-stripped subtree. Root
|
|
27
|
+
layouts and Kimi keep the whole-tree comparison. This resolves the
|
|
28
|
+
flow-architect v0.4.1 and v0.5.0 PARTIAL marketplace install failures;
|
|
29
|
+
already-frozen plans reconcile unchanged because the sealed digest, plan
|
|
30
|
+
schema, and prepare-side freezing are untouched.
|
|
31
|
+
- **Claude `.in_use` transport marker exemption**: the Claude CLI writes an
|
|
32
|
+
empty `.in_use` marker into the plugin install root; it is now exempted as
|
|
33
|
+
consumer-owned transport metadata alongside the existing Codex/Kimi `.git`
|
|
34
|
+
exemption, through a single shared exclusion helper that also backs the
|
|
35
|
+
observe-time diagnostic fallback. Exemptions remain root-only: nested
|
|
36
|
+
markers, extra payload, byte tampering, and sealed authority tampering
|
|
37
|
+
still fail closed.
|
|
38
|
+
- **Regression coverage**: new protocol-level fake-CLI tests cover the
|
|
39
|
+
subdirectory claude cycle with `.in_use` (including byte-tamper,
|
|
40
|
+
extra-file, missing-file, `.git`-not-exempt, and nested-marker negatives),
|
|
41
|
+
fail-closed manifest anomalies (duplicate plugin entry, wrong marketplace
|
|
42
|
+
name, empty source projection), root-layout claude with `.in_use`, and a
|
|
43
|
+
codex subdirectory variant.
|
|
32
44
|
<!-- release-skill:managed:end id=latest-release -->
|
|
33
45
|
|
|
34
46
|
<!-- release-skill:capability:external-write-boundary -->
|
|
35
|
-
> **Current boundary:** v0.1.
|
|
36
|
-
> published status before the
|
|
47
|
+
> **Current boundary:** v0.1.9 is the current release (v0.1.8 previously held
|
|
48
|
+
> published status before the marketplace payload binding fix was added).
|
|
37
49
|
> v0.1.1 completed a real production release to GitHub and npm — the first
|
|
38
50
|
> production-verified milestone — followed by
|
|
39
51
|
> exact npm installation and Claude/Codex consumer installation verification
|
|
@@ -50,7 +62,7 @@ v0.1.8 adds Kimi Code as a first-class plugin host without rewriting the already
|
|
|
50
62
|
> publish global preflight.
|
|
51
63
|
|
|
52
64
|
<!-- release-skill:capability:safe-first-command -->
|
|
53
|
-
> **Production path verified since the v0.1.1 milestone; v0.1.
|
|
65
|
+
> **Production path verified since the v0.1.1 milestone; v0.1.9 is the current
|
|
54
66
|
> release.** The npm-installed CLI is the supported user entry. Source checkout
|
|
55
67
|
> is the development/contributor fallback.
|
|
56
68
|
>
|
package/README.zh-CN.md
CHANGED
|
@@ -2,31 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) · 安装指南:[中文](INSTALL.zh-CN.md) / [English](INSTALL.md)
|
|
4
4
|
|
|
5
|
-
<!-- release-skill:release-version: 0.1.
|
|
5
|
+
<!-- release-skill:release-version: 0.1.9 -->
|
|
6
6
|
面向 Claude Code、Codex 和 Kimi Code 的发布准备工具,完整保留人工维护的文件内容。
|
|
7
7
|
|
|
8
8
|
release-skill 帮助维护者回答三个问题:准备发布什么、还有哪些检查未通过、最终发布的内容是什么。它先冻结并供人工审阅,再从同一份冻结产物发布,不会在最后一步重新生成 README、重新打包当前工作区或覆盖人工内容。
|
|
9
9
|
|
|
10
10
|
<!-- release-skill:managed:start id=latest-release -->
|
|
11
|
-
**0.1.
|
|
12
|
-
|
|
13
|
-
v0.1.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`
|
|
11
|
+
**0.1.9** (2026-07-23)
|
|
12
|
+
|
|
13
|
+
v0.1.9 修复一个结构性的 marketplace 安装校验失败:声明插件 `source` 子目录(如 `./adapters/claude`)的消费者安装现在能把安装载荷绑定到密封整 unit 快照中声明的子树,Claude 根目录 `.in_use` 标记作为消费者所有的传输元数据获得豁免。密封快照摘要、发布计划 schema 与 prepare 冻结均未改动,已冻结计划无需重新审批即可 reconcile。npm 包名(`release-skill`)、发布身份(`publisher: mzdbxqh`)、公开仓库(`ifoohoo/release-skill`)与公司维护主体保持不变。
|
|
14
|
+
|
|
15
|
+
**修复**
|
|
16
|
+
|
|
17
|
+
- **子目录 source 布局的 marketplace 载荷绑定**:消费者清单可以声明
|
|
18
|
+
`./adapters/claude` 这样的插件 `source` 子目录,此时消费者 CLI 只安装该子树,
|
|
19
|
+
而密封权威是整个 unit 快照。安装校验现在先重验密封整快照摘要,再从摘要已验证
|
|
20
|
+
的快照条目内部读取清单声明的 source,并把安装载荷绑定到剥离前缀后的子树;
|
|
21
|
+
根布局与 Kimi 保持整树比较。这解决了 flow-architect v0.4.1 与 v0.5.0 的
|
|
22
|
+
marketplace 安装 PARTIAL 失败;密封摘要、计划 schema 与 prepare 冻结均未改动,
|
|
23
|
+
已冻结计划可以不变地 reconcile 收口。
|
|
24
|
+
- **Claude `.in_use` 传输标记豁免**:Claude CLI 会在插件安装根目录写入空的
|
|
25
|
+
`.in_use` 标记,现在与既有的 Codex/Kimi `.git` 豁免一样作为消费者所有的传输
|
|
26
|
+
元数据豁免,并统一为单一共享 helper(observe 诊断回退同样复用)。豁免仅限根
|
|
27
|
+
直接子项:嵌套标记、多余载荷、字节篡改与密封权威篡改仍然失败关闭。
|
|
28
|
+
- **回归覆盖**:新增协议级 fake-CLI 测试覆盖带 `.in_use` 的子目录 claude
|
|
29
|
+
完整链路(含字节篡改、多余文件、缺失文件、`.git` 不豁免与嵌套标记负例)、
|
|
30
|
+
失败关闭的清单异常(重复插件条目、marketplace 名不符、空 source 投影)、
|
|
31
|
+
带 `.in_use` 的根布局 claude,以及 codex 子目录变体。
|
|
26
32
|
<!-- release-skill:managed:end id=latest-release -->
|
|
27
33
|
|
|
28
34
|
<!-- release-skill:capability:external-write-boundary -->
|
|
29
|
-
> **当前边界:** v0.1.
|
|
35
|
+
> **当前边界:** v0.1.9 是当前发布版本(v0.1.8 曾处于已发布、待独立验证状态)。
|
|
30
36
|
> v0.1.1 已完成 GitHub 与 npm 的
|
|
31
37
|
> 真实生产发布,是首次生产验证的历史里程碑,并从冻结 Git ref 完成精确 npm
|
|
32
38
|
> 安装及 Claude/Codex 消费者安装验证;“当前发布版本”与“首次生产验证里程碑”
|
|
@@ -39,7 +45,7 @@ v0.1.8 在不改写已经公开的 v0.1.7 制品的前提下,新增对 Kimi Co
|
|
|
39
45
|
> 远端唯一性检查在 `publish` 全局预检执行。
|
|
40
46
|
|
|
41
47
|
<!-- release-skill:capability:safe-first-command -->
|
|
42
|
-
> **生产路径自 v0.1.1 里程碑起已完成真实生产验证;v0.1.
|
|
48
|
+
> **生产路径自 v0.1.1 里程碑起已完成真实生产验证;v0.1.9 是当前发布版本。**
|
|
43
49
|
> npm 安装的 CLI 是受支持的用户入口;源码 checkout 保留为开发/贡献者路径。
|
|
44
50
|
>
|
|
45
51
|
> **第一条命令:**
|
|
@@ -9,7 +9,7 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
|
|
|
9
9
|
// Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
|
|
10
10
|
const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
|
|
11
11
|
// Package identity injected at build time — closure-independent --version probe.
|
|
12
|
-
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.
|
|
12
|
+
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.9"});
|
|
13
13
|
|
|
14
14
|
var __create = Object.create;
|
|
15
15
|
var __defProp = Object.defineProperty;
|
|
@@ -76930,6 +76930,49 @@ function transportPayload(entries) {
|
|
|
76930
76930
|
contentDigest
|
|
76931
76931
|
}));
|
|
76932
76932
|
}
|
|
76933
|
+
function consumerTransportExclusions(consumer) {
|
|
76934
|
+
if (consumer === "claude") return [".in_use"];
|
|
76935
|
+
if (consumer === "codex" || consumer === "kimi") return [".git"];
|
|
76936
|
+
return [];
|
|
76937
|
+
}
|
|
76938
|
+
function extractDeclaredPluginSource(consumer, entry) {
|
|
76939
|
+
const rawSource = consumer === "claude" ? entry.source : entry.source?.source === "local" ? entry.source?.path : null;
|
|
76940
|
+
if (typeof rawSource !== "string" || rawSource.length === 0) {
|
|
76941
|
+
throw new Error(`marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`);
|
|
76942
|
+
}
|
|
76943
|
+
if (rawSource.startsWith("/") || rawSource.includes("..") || rawSource.includes("\\") || /^https?:\/\//i.test(rawSource)) {
|
|
76944
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76945
|
+
}
|
|
76946
|
+
const segments = rawSource.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
76947
|
+
if (segments.some((segment) => segment === "..")) {
|
|
76948
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76949
|
+
}
|
|
76950
|
+
return segments.length === 0 ? "." : segments.join("/");
|
|
76951
|
+
}
|
|
76952
|
+
async function resolveInstalledPayloadSubpath(snapshotDir, sourceEntries, action, consumer) {
|
|
76953
|
+
if (consumer === "kimi") return ".";
|
|
76954
|
+
const marketplaceRelative = consumer === "claude" ? ".claude-plugin/marketplace.json" : ".agents/plugins/marketplace.json";
|
|
76955
|
+
const anchored = sourceEntries.some((entry) => entry.type === "file" && entry.path === marketplaceRelative);
|
|
76956
|
+
if (!anchored) {
|
|
76957
|
+
throw new Error(`frozen snapshot is missing the marketplace manifest ${marketplaceRelative}`);
|
|
76958
|
+
}
|
|
76959
|
+
const result = await validateManifestFile(resolve19(snapshotDir, marketplaceRelative), ["name", "plugins"]);
|
|
76960
|
+
if (!result.valid) {
|
|
76961
|
+
throw new Error(`frozen snapshot ${marketplaceRelative} invalid: ${result.error}`);
|
|
76962
|
+
}
|
|
76963
|
+
if (result.manifest.name !== action.marketplace) {
|
|
76964
|
+
throw new Error(`marketplace manifest name "${result.manifest.name}" does not match action marketplace "${action.marketplace}"`);
|
|
76965
|
+
}
|
|
76966
|
+
const plugins = result.manifest.plugins;
|
|
76967
|
+
if (!Array.isArray(plugins)) {
|
|
76968
|
+
throw new Error(`${marketplaceRelative} must have a plugins[] array`);
|
|
76969
|
+
}
|
|
76970
|
+
const matches = plugins.filter((entry) => entry.name === action.plugin);
|
|
76971
|
+
if (matches.length !== 1) {
|
|
76972
|
+
throw new Error(`expected exactly one plugins[] entry with name "${action.plugin}", found ${matches.length}`);
|
|
76973
|
+
}
|
|
76974
|
+
return extractDeclaredPluginSource(consumer, matches[0]);
|
|
76975
|
+
}
|
|
76933
76976
|
async function verifyInstalledMarketplacePayload(action, context, installPath, consumer) {
|
|
76934
76977
|
const sourcePath = await resolveFrozenPath(
|
|
76935
76978
|
context.root,
|
|
@@ -76940,10 +76983,21 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
|
|
|
76940
76983
|
if (sourceSnapshot.digest !== action.manifestDigest) {
|
|
76941
76984
|
throw new Error("frozen marketplace snapshot digest no longer matches the plan");
|
|
76942
76985
|
}
|
|
76986
|
+
const payloadSubpath = await resolveInstalledPayloadSubpath(
|
|
76987
|
+
sourcePath,
|
|
76988
|
+
sourceSnapshot.entries,
|
|
76989
|
+
action,
|
|
76990
|
+
consumer
|
|
76991
|
+
);
|
|
76992
|
+
const prefix = payloadSubpath === "." ? null : `${payloadSubpath}/`;
|
|
76993
|
+
const authorityEntries = prefix === null ? sourceSnapshot.entries : sourceSnapshot.entries.filter((entry) => entry.path.startsWith(prefix)).map((entry) => ({ ...entry, path: entry.path.slice(prefix.length) }));
|
|
76994
|
+
if (authorityEntries.length === 0) {
|
|
76995
|
+
throw new Error("frozen snapshot contains no payload under the declared marketplace source");
|
|
76996
|
+
}
|
|
76943
76997
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
76944
|
-
excludeRootEntries: consumer
|
|
76998
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
76945
76999
|
});
|
|
76946
|
-
if (JSON.stringify(transportPayload(
|
|
77000
|
+
if (JSON.stringify(transportPayload(authorityEntries)) !== JSON.stringify(transportPayload(installedSnapshot.entries))) {
|
|
76947
77001
|
throw new Error("installed marketplace payload differs in path, bytes, size, or non-write mode bits");
|
|
76948
77002
|
}
|
|
76949
77003
|
return action.manifestDigest;
|
|
@@ -77643,19 +77697,14 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
77643
77697
|
});
|
|
77644
77698
|
}
|
|
77645
77699
|
const entry = pluginEntry[0];
|
|
77646
|
-
|
|
77647
|
-
|
|
77648
|
-
|
|
77649
|
-
|
|
77650
|
-
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77651
|
-
error: `marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`
|
|
77652
|
-
});
|
|
77653
|
-
}
|
|
77654
|
-
if (sourcePath.startsWith("/") || sourcePath.includes("..") || sourcePath.includes("\\") || /^https?:\/\//i.test(sourcePath)) {
|
|
77700
|
+
let sourcePath;
|
|
77701
|
+
try {
|
|
77702
|
+
sourcePath = extractDeclaredPluginSource(consumer, entry);
|
|
77703
|
+
} catch (sourceErr) {
|
|
77655
77704
|
return createResult({
|
|
77656
77705
|
actionType,
|
|
77657
77706
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77658
|
-
error:
|
|
77707
|
+
error: sourceErr.message
|
|
77659
77708
|
});
|
|
77660
77709
|
}
|
|
77661
77710
|
const sourceDirAbs = resolve19(snapshotDirReal, sourcePath);
|
|
@@ -78578,7 +78627,7 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
78578
78627
|
} catch (digestErr) {
|
|
78579
78628
|
try {
|
|
78580
78629
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
78581
|
-
excludeRootEntries: consumer
|
|
78630
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
78582
78631
|
});
|
|
78583
78632
|
manifestDigest = installedSnapshot.digest;
|
|
78584
78633
|
} catch {
|
|
@@ -78744,6 +78793,9 @@ var init_plugin_marketplace = __esm({
|
|
|
78744
78793
|
execFile9 = promisify10(execFileCb10);
|
|
78745
78794
|
NAME4 = "plugin-marketplace";
|
|
78746
78795
|
__name(transportPayload, "transportPayload");
|
|
78796
|
+
__name(consumerTransportExclusions, "consumerTransportExclusions");
|
|
78797
|
+
__name(extractDeclaredPluginSource, "extractDeclaredPluginSource");
|
|
78798
|
+
__name(resolveInstalledPayloadSubpath, "resolveInstalledPayloadSubpath");
|
|
78747
78799
|
__name(verifyInstalledMarketplacePayload, "verifyInstalledMarketplacePayload");
|
|
78748
78800
|
__name(writeEvidenceAtomic, "writeEvidenceAtomic");
|
|
78749
78801
|
KIMI_REQUIREMENT_FILE = "release-skill-kimi-manual-install.json";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "release-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Safe preparation and frozen GitHub/npm production publishing with full happy end verification",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "广州市风荷科技有限公司"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"defaultPrompt": [
|
|
21
21
|
"Assess this project for release readiness.",
|
|
22
|
-
"Prepare a release plan for version 0.1.
|
|
22
|
+
"Prepare a release plan for version 0.1.9.",
|
|
23
23
|
"Help me understand the release workflow."
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -9,7 +9,7 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
|
|
|
9
9
|
// Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
|
|
10
10
|
const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
|
|
11
11
|
// Package identity injected at build time — closure-independent --version probe.
|
|
12
|
-
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.
|
|
12
|
+
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.9"});
|
|
13
13
|
|
|
14
14
|
var __create = Object.create;
|
|
15
15
|
var __defProp = Object.defineProperty;
|
|
@@ -76930,6 +76930,49 @@ function transportPayload(entries) {
|
|
|
76930
76930
|
contentDigest
|
|
76931
76931
|
}));
|
|
76932
76932
|
}
|
|
76933
|
+
function consumerTransportExclusions(consumer) {
|
|
76934
|
+
if (consumer === "claude") return [".in_use"];
|
|
76935
|
+
if (consumer === "codex" || consumer === "kimi") return [".git"];
|
|
76936
|
+
return [];
|
|
76937
|
+
}
|
|
76938
|
+
function extractDeclaredPluginSource(consumer, entry) {
|
|
76939
|
+
const rawSource = consumer === "claude" ? entry.source : entry.source?.source === "local" ? entry.source?.path : null;
|
|
76940
|
+
if (typeof rawSource !== "string" || rawSource.length === 0) {
|
|
76941
|
+
throw new Error(`marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`);
|
|
76942
|
+
}
|
|
76943
|
+
if (rawSource.startsWith("/") || rawSource.includes("..") || rawSource.includes("\\") || /^https?:\/\//i.test(rawSource)) {
|
|
76944
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76945
|
+
}
|
|
76946
|
+
const segments = rawSource.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
76947
|
+
if (segments.some((segment) => segment === "..")) {
|
|
76948
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76949
|
+
}
|
|
76950
|
+
return segments.length === 0 ? "." : segments.join("/");
|
|
76951
|
+
}
|
|
76952
|
+
async function resolveInstalledPayloadSubpath(snapshotDir, sourceEntries, action, consumer) {
|
|
76953
|
+
if (consumer === "kimi") return ".";
|
|
76954
|
+
const marketplaceRelative = consumer === "claude" ? ".claude-plugin/marketplace.json" : ".agents/plugins/marketplace.json";
|
|
76955
|
+
const anchored = sourceEntries.some((entry) => entry.type === "file" && entry.path === marketplaceRelative);
|
|
76956
|
+
if (!anchored) {
|
|
76957
|
+
throw new Error(`frozen snapshot is missing the marketplace manifest ${marketplaceRelative}`);
|
|
76958
|
+
}
|
|
76959
|
+
const result = await validateManifestFile(resolve19(snapshotDir, marketplaceRelative), ["name", "plugins"]);
|
|
76960
|
+
if (!result.valid) {
|
|
76961
|
+
throw new Error(`frozen snapshot ${marketplaceRelative} invalid: ${result.error}`);
|
|
76962
|
+
}
|
|
76963
|
+
if (result.manifest.name !== action.marketplace) {
|
|
76964
|
+
throw new Error(`marketplace manifest name "${result.manifest.name}" does not match action marketplace "${action.marketplace}"`);
|
|
76965
|
+
}
|
|
76966
|
+
const plugins = result.manifest.plugins;
|
|
76967
|
+
if (!Array.isArray(plugins)) {
|
|
76968
|
+
throw new Error(`${marketplaceRelative} must have a plugins[] array`);
|
|
76969
|
+
}
|
|
76970
|
+
const matches = plugins.filter((entry) => entry.name === action.plugin);
|
|
76971
|
+
if (matches.length !== 1) {
|
|
76972
|
+
throw new Error(`expected exactly one plugins[] entry with name "${action.plugin}", found ${matches.length}`);
|
|
76973
|
+
}
|
|
76974
|
+
return extractDeclaredPluginSource(consumer, matches[0]);
|
|
76975
|
+
}
|
|
76933
76976
|
async function verifyInstalledMarketplacePayload(action, context, installPath, consumer) {
|
|
76934
76977
|
const sourcePath = await resolveFrozenPath(
|
|
76935
76978
|
context.root,
|
|
@@ -76940,10 +76983,21 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
|
|
|
76940
76983
|
if (sourceSnapshot.digest !== action.manifestDigest) {
|
|
76941
76984
|
throw new Error("frozen marketplace snapshot digest no longer matches the plan");
|
|
76942
76985
|
}
|
|
76986
|
+
const payloadSubpath = await resolveInstalledPayloadSubpath(
|
|
76987
|
+
sourcePath,
|
|
76988
|
+
sourceSnapshot.entries,
|
|
76989
|
+
action,
|
|
76990
|
+
consumer
|
|
76991
|
+
);
|
|
76992
|
+
const prefix = payloadSubpath === "." ? null : `${payloadSubpath}/`;
|
|
76993
|
+
const authorityEntries = prefix === null ? sourceSnapshot.entries : sourceSnapshot.entries.filter((entry) => entry.path.startsWith(prefix)).map((entry) => ({ ...entry, path: entry.path.slice(prefix.length) }));
|
|
76994
|
+
if (authorityEntries.length === 0) {
|
|
76995
|
+
throw new Error("frozen snapshot contains no payload under the declared marketplace source");
|
|
76996
|
+
}
|
|
76943
76997
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
76944
|
-
excludeRootEntries: consumer
|
|
76998
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
76945
76999
|
});
|
|
76946
|
-
if (JSON.stringify(transportPayload(
|
|
77000
|
+
if (JSON.stringify(transportPayload(authorityEntries)) !== JSON.stringify(transportPayload(installedSnapshot.entries))) {
|
|
76947
77001
|
throw new Error("installed marketplace payload differs in path, bytes, size, or non-write mode bits");
|
|
76948
77002
|
}
|
|
76949
77003
|
return action.manifestDigest;
|
|
@@ -77643,19 +77697,14 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
77643
77697
|
});
|
|
77644
77698
|
}
|
|
77645
77699
|
const entry = pluginEntry[0];
|
|
77646
|
-
|
|
77647
|
-
|
|
77648
|
-
|
|
77649
|
-
|
|
77650
|
-
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77651
|
-
error: `marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`
|
|
77652
|
-
});
|
|
77653
|
-
}
|
|
77654
|
-
if (sourcePath.startsWith("/") || sourcePath.includes("..") || sourcePath.includes("\\") || /^https?:\/\//i.test(sourcePath)) {
|
|
77700
|
+
let sourcePath;
|
|
77701
|
+
try {
|
|
77702
|
+
sourcePath = extractDeclaredPluginSource(consumer, entry);
|
|
77703
|
+
} catch (sourceErr) {
|
|
77655
77704
|
return createResult({
|
|
77656
77705
|
actionType,
|
|
77657
77706
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77658
|
-
error:
|
|
77707
|
+
error: sourceErr.message
|
|
77659
77708
|
});
|
|
77660
77709
|
}
|
|
77661
77710
|
const sourceDirAbs = resolve19(snapshotDirReal, sourcePath);
|
|
@@ -78578,7 +78627,7 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
78578
78627
|
} catch (digestErr) {
|
|
78579
78628
|
try {
|
|
78580
78629
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
78581
|
-
excludeRootEntries: consumer
|
|
78630
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
78582
78631
|
});
|
|
78583
78632
|
manifestDigest = installedSnapshot.digest;
|
|
78584
78633
|
} catch {
|
|
@@ -78744,6 +78793,9 @@ var init_plugin_marketplace = __esm({
|
|
|
78744
78793
|
execFile9 = promisify10(execFileCb10);
|
|
78745
78794
|
NAME4 = "plugin-marketplace";
|
|
78746
78795
|
__name(transportPayload, "transportPayload");
|
|
78796
|
+
__name(consumerTransportExclusions, "consumerTransportExclusions");
|
|
78797
|
+
__name(extractDeclaredPluginSource, "extractDeclaredPluginSource");
|
|
78798
|
+
__name(resolveInstalledPayloadSubpath, "resolveInstalledPayloadSubpath");
|
|
78747
78799
|
__name(verifyInstalledMarketplacePayload, "verifyInstalledMarketplacePayload");
|
|
78748
78800
|
__name(writeEvidenceAtomic, "writeEvidenceAtomic");
|
|
78749
78801
|
KIMI_REQUIREMENT_FILE = "release-skill-kimi-manual-install.json";
|
|
@@ -9,7 +9,7 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
|
|
|
9
9
|
// Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
|
|
10
10
|
const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
|
|
11
11
|
// Package identity injected at build time — closure-independent --version probe.
|
|
12
|
-
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.
|
|
12
|
+
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.9"});
|
|
13
13
|
|
|
14
14
|
var __create = Object.create;
|
|
15
15
|
var __defProp = Object.defineProperty;
|
|
@@ -76930,6 +76930,49 @@ function transportPayload(entries) {
|
|
|
76930
76930
|
contentDigest
|
|
76931
76931
|
}));
|
|
76932
76932
|
}
|
|
76933
|
+
function consumerTransportExclusions(consumer) {
|
|
76934
|
+
if (consumer === "claude") return [".in_use"];
|
|
76935
|
+
if (consumer === "codex" || consumer === "kimi") return [".git"];
|
|
76936
|
+
return [];
|
|
76937
|
+
}
|
|
76938
|
+
function extractDeclaredPluginSource(consumer, entry) {
|
|
76939
|
+
const rawSource = consumer === "claude" ? entry.source : entry.source?.source === "local" ? entry.source?.path : null;
|
|
76940
|
+
if (typeof rawSource !== "string" || rawSource.length === 0) {
|
|
76941
|
+
throw new Error(`marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`);
|
|
76942
|
+
}
|
|
76943
|
+
if (rawSource.startsWith("/") || rawSource.includes("..") || rawSource.includes("\\") || /^https?:\/\//i.test(rawSource)) {
|
|
76944
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76945
|
+
}
|
|
76946
|
+
const segments = rawSource.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
76947
|
+
if (segments.some((segment) => segment === "..")) {
|
|
76948
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76949
|
+
}
|
|
76950
|
+
return segments.length === 0 ? "." : segments.join("/");
|
|
76951
|
+
}
|
|
76952
|
+
async function resolveInstalledPayloadSubpath(snapshotDir, sourceEntries, action, consumer) {
|
|
76953
|
+
if (consumer === "kimi") return ".";
|
|
76954
|
+
const marketplaceRelative = consumer === "claude" ? ".claude-plugin/marketplace.json" : ".agents/plugins/marketplace.json";
|
|
76955
|
+
const anchored = sourceEntries.some((entry) => entry.type === "file" && entry.path === marketplaceRelative);
|
|
76956
|
+
if (!anchored) {
|
|
76957
|
+
throw new Error(`frozen snapshot is missing the marketplace manifest ${marketplaceRelative}`);
|
|
76958
|
+
}
|
|
76959
|
+
const result = await validateManifestFile(resolve19(snapshotDir, marketplaceRelative), ["name", "plugins"]);
|
|
76960
|
+
if (!result.valid) {
|
|
76961
|
+
throw new Error(`frozen snapshot ${marketplaceRelative} invalid: ${result.error}`);
|
|
76962
|
+
}
|
|
76963
|
+
if (result.manifest.name !== action.marketplace) {
|
|
76964
|
+
throw new Error(`marketplace manifest name "${result.manifest.name}" does not match action marketplace "${action.marketplace}"`);
|
|
76965
|
+
}
|
|
76966
|
+
const plugins = result.manifest.plugins;
|
|
76967
|
+
if (!Array.isArray(plugins)) {
|
|
76968
|
+
throw new Error(`${marketplaceRelative} must have a plugins[] array`);
|
|
76969
|
+
}
|
|
76970
|
+
const matches = plugins.filter((entry) => entry.name === action.plugin);
|
|
76971
|
+
if (matches.length !== 1) {
|
|
76972
|
+
throw new Error(`expected exactly one plugins[] entry with name "${action.plugin}", found ${matches.length}`);
|
|
76973
|
+
}
|
|
76974
|
+
return extractDeclaredPluginSource(consumer, matches[0]);
|
|
76975
|
+
}
|
|
76933
76976
|
async function verifyInstalledMarketplacePayload(action, context, installPath, consumer) {
|
|
76934
76977
|
const sourcePath = await resolveFrozenPath(
|
|
76935
76978
|
context.root,
|
|
@@ -76940,10 +76983,21 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
|
|
|
76940
76983
|
if (sourceSnapshot.digest !== action.manifestDigest) {
|
|
76941
76984
|
throw new Error("frozen marketplace snapshot digest no longer matches the plan");
|
|
76942
76985
|
}
|
|
76986
|
+
const payloadSubpath = await resolveInstalledPayloadSubpath(
|
|
76987
|
+
sourcePath,
|
|
76988
|
+
sourceSnapshot.entries,
|
|
76989
|
+
action,
|
|
76990
|
+
consumer
|
|
76991
|
+
);
|
|
76992
|
+
const prefix = payloadSubpath === "." ? null : `${payloadSubpath}/`;
|
|
76993
|
+
const authorityEntries = prefix === null ? sourceSnapshot.entries : sourceSnapshot.entries.filter((entry) => entry.path.startsWith(prefix)).map((entry) => ({ ...entry, path: entry.path.slice(prefix.length) }));
|
|
76994
|
+
if (authorityEntries.length === 0) {
|
|
76995
|
+
throw new Error("frozen snapshot contains no payload under the declared marketplace source");
|
|
76996
|
+
}
|
|
76943
76997
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
76944
|
-
excludeRootEntries: consumer
|
|
76998
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
76945
76999
|
});
|
|
76946
|
-
if (JSON.stringify(transportPayload(
|
|
77000
|
+
if (JSON.stringify(transportPayload(authorityEntries)) !== JSON.stringify(transportPayload(installedSnapshot.entries))) {
|
|
76947
77001
|
throw new Error("installed marketplace payload differs in path, bytes, size, or non-write mode bits");
|
|
76948
77002
|
}
|
|
76949
77003
|
return action.manifestDigest;
|
|
@@ -77643,19 +77697,14 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
77643
77697
|
});
|
|
77644
77698
|
}
|
|
77645
77699
|
const entry = pluginEntry[0];
|
|
77646
|
-
|
|
77647
|
-
|
|
77648
|
-
|
|
77649
|
-
|
|
77650
|
-
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77651
|
-
error: `marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`
|
|
77652
|
-
});
|
|
77653
|
-
}
|
|
77654
|
-
if (sourcePath.startsWith("/") || sourcePath.includes("..") || sourcePath.includes("\\") || /^https?:\/\//i.test(sourcePath)) {
|
|
77700
|
+
let sourcePath;
|
|
77701
|
+
try {
|
|
77702
|
+
sourcePath = extractDeclaredPluginSource(consumer, entry);
|
|
77703
|
+
} catch (sourceErr) {
|
|
77655
77704
|
return createResult({
|
|
77656
77705
|
actionType,
|
|
77657
77706
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77658
|
-
error:
|
|
77707
|
+
error: sourceErr.message
|
|
77659
77708
|
});
|
|
77660
77709
|
}
|
|
77661
77710
|
const sourceDirAbs = resolve19(snapshotDirReal, sourcePath);
|
|
@@ -78578,7 +78627,7 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
78578
78627
|
} catch (digestErr) {
|
|
78579
78628
|
try {
|
|
78580
78629
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
78581
|
-
excludeRootEntries: consumer
|
|
78630
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
78582
78631
|
});
|
|
78583
78632
|
manifestDigest = installedSnapshot.digest;
|
|
78584
78633
|
} catch {
|
|
@@ -78744,6 +78793,9 @@ var init_plugin_marketplace = __esm({
|
|
|
78744
78793
|
execFile9 = promisify10(execFileCb10);
|
|
78745
78794
|
NAME4 = "plugin-marketplace";
|
|
78746
78795
|
__name(transportPayload, "transportPayload");
|
|
78796
|
+
__name(consumerTransportExclusions, "consumerTransportExclusions");
|
|
78797
|
+
__name(extractDeclaredPluginSource, "extractDeclaredPluginSource");
|
|
78798
|
+
__name(resolveInstalledPayloadSubpath, "resolveInstalledPayloadSubpath");
|
|
78747
78799
|
__name(verifyInstalledMarketplacePayload, "verifyInstalledMarketplacePayload");
|
|
78748
78800
|
__name(writeEvidenceAtomic, "writeEvidenceAtomic");
|
|
78749
78801
|
KIMI_REQUIREMENT_FILE = "release-skill-kimi-manual-install.json";
|
|
@@ -9,7 +9,7 @@ const __bundlePkgRoot = __bundleResolve(__bundleDirname(__bundleFileURLToPath(im
|
|
|
9
9
|
// Provide a real require() for CJS packages bundled into ESM (e.g. yaml, ajv).
|
|
10
10
|
const __bundleRealRequire = __bundleCreateRequire(import.meta.url);
|
|
11
11
|
// Package identity injected at build time — closure-independent --version probe.
|
|
12
|
-
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.
|
|
12
|
+
const __bundlePkg = Object.freeze({"name":"release-skill","version":"0.1.9"});
|
|
13
13
|
|
|
14
14
|
var __create = Object.create;
|
|
15
15
|
var __defProp = Object.defineProperty;
|
|
@@ -76930,6 +76930,49 @@ function transportPayload(entries) {
|
|
|
76930
76930
|
contentDigest
|
|
76931
76931
|
}));
|
|
76932
76932
|
}
|
|
76933
|
+
function consumerTransportExclusions(consumer) {
|
|
76934
|
+
if (consumer === "claude") return [".in_use"];
|
|
76935
|
+
if (consumer === "codex" || consumer === "kimi") return [".git"];
|
|
76936
|
+
return [];
|
|
76937
|
+
}
|
|
76938
|
+
function extractDeclaredPluginSource(consumer, entry) {
|
|
76939
|
+
const rawSource = consumer === "claude" ? entry.source : entry.source?.source === "local" ? entry.source?.path : null;
|
|
76940
|
+
if (typeof rawSource !== "string" || rawSource.length === 0) {
|
|
76941
|
+
throw new Error(`marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`);
|
|
76942
|
+
}
|
|
76943
|
+
if (rawSource.startsWith("/") || rawSource.includes("..") || rawSource.includes("\\") || /^https?:\/\//i.test(rawSource)) {
|
|
76944
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76945
|
+
}
|
|
76946
|
+
const segments = rawSource.split("/").filter((segment) => segment !== "" && segment !== ".");
|
|
76947
|
+
if (segments.some((segment) => segment === "..")) {
|
|
76948
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
76949
|
+
}
|
|
76950
|
+
return segments.length === 0 ? "." : segments.join("/");
|
|
76951
|
+
}
|
|
76952
|
+
async function resolveInstalledPayloadSubpath(snapshotDir, sourceEntries, action, consumer) {
|
|
76953
|
+
if (consumer === "kimi") return ".";
|
|
76954
|
+
const marketplaceRelative = consumer === "claude" ? ".claude-plugin/marketplace.json" : ".agents/plugins/marketplace.json";
|
|
76955
|
+
const anchored = sourceEntries.some((entry) => entry.type === "file" && entry.path === marketplaceRelative);
|
|
76956
|
+
if (!anchored) {
|
|
76957
|
+
throw new Error(`frozen snapshot is missing the marketplace manifest ${marketplaceRelative}`);
|
|
76958
|
+
}
|
|
76959
|
+
const result = await validateManifestFile(resolve19(snapshotDir, marketplaceRelative), ["name", "plugins"]);
|
|
76960
|
+
if (!result.valid) {
|
|
76961
|
+
throw new Error(`frozen snapshot ${marketplaceRelative} invalid: ${result.error}`);
|
|
76962
|
+
}
|
|
76963
|
+
if (result.manifest.name !== action.marketplace) {
|
|
76964
|
+
throw new Error(`marketplace manifest name "${result.manifest.name}" does not match action marketplace "${action.marketplace}"`);
|
|
76965
|
+
}
|
|
76966
|
+
const plugins = result.manifest.plugins;
|
|
76967
|
+
if (!Array.isArray(plugins)) {
|
|
76968
|
+
throw new Error(`${marketplaceRelative} must have a plugins[] array`);
|
|
76969
|
+
}
|
|
76970
|
+
const matches = plugins.filter((entry) => entry.name === action.plugin);
|
|
76971
|
+
if (matches.length !== 1) {
|
|
76972
|
+
throw new Error(`expected exactly one plugins[] entry with name "${action.plugin}", found ${matches.length}`);
|
|
76973
|
+
}
|
|
76974
|
+
return extractDeclaredPluginSource(consumer, matches[0]);
|
|
76975
|
+
}
|
|
76933
76976
|
async function verifyInstalledMarketplacePayload(action, context, installPath, consumer) {
|
|
76934
76977
|
const sourcePath = await resolveFrozenPath(
|
|
76935
76978
|
context.root,
|
|
@@ -76940,10 +76983,21 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
|
|
|
76940
76983
|
if (sourceSnapshot.digest !== action.manifestDigest) {
|
|
76941
76984
|
throw new Error("frozen marketplace snapshot digest no longer matches the plan");
|
|
76942
76985
|
}
|
|
76986
|
+
const payloadSubpath = await resolveInstalledPayloadSubpath(
|
|
76987
|
+
sourcePath,
|
|
76988
|
+
sourceSnapshot.entries,
|
|
76989
|
+
action,
|
|
76990
|
+
consumer
|
|
76991
|
+
);
|
|
76992
|
+
const prefix = payloadSubpath === "." ? null : `${payloadSubpath}/`;
|
|
76993
|
+
const authorityEntries = prefix === null ? sourceSnapshot.entries : sourceSnapshot.entries.filter((entry) => entry.path.startsWith(prefix)).map((entry) => ({ ...entry, path: entry.path.slice(prefix.length) }));
|
|
76994
|
+
if (authorityEntries.length === 0) {
|
|
76995
|
+
throw new Error("frozen snapshot contains no payload under the declared marketplace source");
|
|
76996
|
+
}
|
|
76943
76997
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
76944
|
-
excludeRootEntries: consumer
|
|
76998
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
76945
76999
|
});
|
|
76946
|
-
if (JSON.stringify(transportPayload(
|
|
77000
|
+
if (JSON.stringify(transportPayload(authorityEntries)) !== JSON.stringify(transportPayload(installedSnapshot.entries))) {
|
|
76947
77001
|
throw new Error("installed marketplace payload differs in path, bytes, size, or non-write mode bits");
|
|
76948
77002
|
}
|
|
76949
77003
|
return action.manifestDigest;
|
|
@@ -77643,19 +77697,14 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
77643
77697
|
});
|
|
77644
77698
|
}
|
|
77645
77699
|
const entry = pluginEntry[0];
|
|
77646
|
-
|
|
77647
|
-
|
|
77648
|
-
|
|
77649
|
-
|
|
77650
|
-
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77651
|
-
error: `marketplace plugin entry source must be a non-empty relative path${consumer === "codex" ? ' (object with source:"local")' : ""}, got ${JSON.stringify(entry.source)}`
|
|
77652
|
-
});
|
|
77653
|
-
}
|
|
77654
|
-
if (sourcePath.startsWith("/") || sourcePath.includes("..") || sourcePath.includes("\\") || /^https?:\/\//i.test(sourcePath)) {
|
|
77700
|
+
let sourcePath;
|
|
77701
|
+
try {
|
|
77702
|
+
sourcePath = extractDeclaredPluginSource(consumer, entry);
|
|
77703
|
+
} catch (sourceErr) {
|
|
77655
77704
|
return createResult({
|
|
77656
77705
|
actionType,
|
|
77657
77706
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
77658
|
-
error:
|
|
77707
|
+
error: sourceErr.message
|
|
77659
77708
|
});
|
|
77660
77709
|
}
|
|
77661
77710
|
const sourceDirAbs = resolve19(snapshotDirReal, sourcePath);
|
|
@@ -78578,7 +78627,7 @@ function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
78578
78627
|
} catch (digestErr) {
|
|
78579
78628
|
try {
|
|
78580
78629
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
78581
|
-
excludeRootEntries: consumer
|
|
78630
|
+
excludeRootEntries: consumerTransportExclusions(consumer)
|
|
78582
78631
|
});
|
|
78583
78632
|
manifestDigest = installedSnapshot.digest;
|
|
78584
78633
|
} catch {
|
|
@@ -78744,6 +78793,9 @@ var init_plugin_marketplace = __esm({
|
|
|
78744
78793
|
execFile9 = promisify10(execFileCb10);
|
|
78745
78794
|
NAME4 = "plugin-marketplace";
|
|
78746
78795
|
__name(transportPayload, "transportPayload");
|
|
78796
|
+
__name(consumerTransportExclusions, "consumerTransportExclusions");
|
|
78797
|
+
__name(extractDeclaredPluginSource, "extractDeclaredPluginSource");
|
|
78798
|
+
__name(resolveInstalledPayloadSubpath, "resolveInstalledPayloadSubpath");
|
|
78747
78799
|
__name(verifyInstalledMarketplacePayload, "verifyInstalledMarketplacePayload");
|
|
78748
78800
|
__name(writeEvidenceAtomic, "writeEvidenceAtomic");
|
|
78749
78801
|
KIMI_REQUIREMENT_FILE = "release-skill-kimi-manual-install.json";
|
package/package.json
CHANGED
|
@@ -48,6 +48,93 @@ function transportPayload(entries) {
|
|
|
48
48
|
}));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Consumer-owned transport metadata written into the plugin install root
|
|
52
|
+
// that is not part of the published payload. Codex checks out the
|
|
53
|
+
// repository (root `.git` metadata); Claude marks in-use plugin checkouts
|
|
54
|
+
// with an empty root `.in_use` marker. Exclusions apply to root entries
|
|
55
|
+
// only; all payload paths keep the fail-closed file checks.
|
|
56
|
+
function consumerTransportExclusions(consumer) {
|
|
57
|
+
if (consumer === 'claude') return ['.in_use'];
|
|
58
|
+
if (consumer === 'codex' || consumer === 'kimi') return ['.git'];
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract the marketplace plugin entry's declared source as a validated,
|
|
64
|
+
* normalized snapshot-relative subpath ("." for root layouts).
|
|
65
|
+
*
|
|
66
|
+
* The rejection set is preserved verbatim from the preflight safety checks:
|
|
67
|
+
* non-empty string, no absolute paths, no ".." traversal (substring check,
|
|
68
|
+
* deliberately stricter than per-segment), no backslashes, no remote URLs.
|
|
69
|
+
* Normalization runs AFTER validation and collapses "./", ".", and trailing
|
|
70
|
+
* slashes. Throws with the preflight's exact error messages.
|
|
71
|
+
*/
|
|
72
|
+
function extractDeclaredPluginSource(consumer, entry) {
|
|
73
|
+
const rawSource = consumer === 'claude'
|
|
74
|
+
? entry.source
|
|
75
|
+
: entry.source?.source === 'local' ? entry.source?.path : null;
|
|
76
|
+
if (typeof rawSource !== 'string' || rawSource.length === 0) {
|
|
77
|
+
throw new Error(`marketplace plugin entry source must be a non-empty relative path${consumer === 'codex' ? ' (object with source:"local")' : ''}, got ${JSON.stringify(entry.source)}`);
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
rawSource.startsWith('/') ||
|
|
81
|
+
rawSource.includes('..') ||
|
|
82
|
+
rawSource.includes('\\') ||
|
|
83
|
+
/^https?:\/\//i.test(rawSource)
|
|
84
|
+
) {
|
|
85
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
86
|
+
}
|
|
87
|
+
const segments = rawSource.split('/').filter((segment) => segment !== '' && segment !== '.');
|
|
88
|
+
// Redundant post-normalization invariant: ".." was already rejected by the
|
|
89
|
+
// substring check above; fail closed if it ever survives normalization.
|
|
90
|
+
if (segments.some((segment) => segment === '..')) {
|
|
91
|
+
throw new Error(`marketplace plugin entry source "${rawSource}" is not a safe relative path`);
|
|
92
|
+
}
|
|
93
|
+
return segments.length === 0 ? '.' : segments.join('/');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the payload subpath the consumer CLI installs for this action,
|
|
98
|
+
* read from the marketplace manifest inside the digest-verified frozen
|
|
99
|
+
* snapshot. Returns "." when the whole snapshot is the installed payload
|
|
100
|
+
* (root layouts, and kimi which has no marketplace manifest).
|
|
101
|
+
*
|
|
102
|
+
* Throws (fail closed) if the manifest is absent from the verified entries,
|
|
103
|
+
* unreadable, names a different marketplace, or does not declare exactly
|
|
104
|
+
* one plugins[] entry for action.plugin. The manifest itself lives inside
|
|
105
|
+
* the digest-sealed snapshot, so the declared subpath is authority-bound:
|
|
106
|
+
* tampering with it fails the snapshot digest revalidation first.
|
|
107
|
+
*/
|
|
108
|
+
async function resolveInstalledPayloadSubpath(snapshotDir, sourceEntries, action, consumer) {
|
|
109
|
+
if (consumer === 'kimi') return '.';
|
|
110
|
+
const marketplaceRelative = consumer === 'claude'
|
|
111
|
+
? '.claude-plugin/marketplace.json'
|
|
112
|
+
: '.agents/plugins/marketplace.json';
|
|
113
|
+
// Anchor the manifest read to the digest-verified entry walk: the target
|
|
114
|
+
// must be one of the regular files that already passed the fail-closed
|
|
115
|
+
// read checks (O_NOFOLLOW, single link, before/after stat stability).
|
|
116
|
+
const anchored = sourceEntries.some((entry) => entry.type === 'file' && entry.path === marketplaceRelative);
|
|
117
|
+
if (!anchored) {
|
|
118
|
+
throw new Error(`frozen snapshot is missing the marketplace manifest ${marketplaceRelative}`);
|
|
119
|
+
}
|
|
120
|
+
const result = await validateManifestFile(resolve(snapshotDir, marketplaceRelative), ['name', 'plugins']);
|
|
121
|
+
if (!result.valid) {
|
|
122
|
+
throw new Error(`frozen snapshot ${marketplaceRelative} invalid: ${result.error}`);
|
|
123
|
+
}
|
|
124
|
+
if (result.manifest.name !== action.marketplace) {
|
|
125
|
+
throw new Error(`marketplace manifest name "${result.manifest.name}" does not match action marketplace "${action.marketplace}"`);
|
|
126
|
+
}
|
|
127
|
+
const plugins = result.manifest.plugins;
|
|
128
|
+
if (!Array.isArray(plugins)) {
|
|
129
|
+
throw new Error(`${marketplaceRelative} must have a plugins[] array`);
|
|
130
|
+
}
|
|
131
|
+
const matches = plugins.filter((entry) => entry.name === action.plugin);
|
|
132
|
+
if (matches.length !== 1) {
|
|
133
|
+
throw new Error(`expected exactly one plugins[] entry with name "${action.plugin}", found ${matches.length}`);
|
|
134
|
+
}
|
|
135
|
+
return extractDeclaredPluginSource(consumer, matches[0]);
|
|
136
|
+
}
|
|
137
|
+
|
|
51
138
|
async function verifyInstalledMarketplacePayload(action, context, installPath, consumer) {
|
|
52
139
|
const sourcePath = await resolveFrozenPath(
|
|
53
140
|
context.root,
|
|
@@ -58,11 +145,34 @@ async function verifyInstalledMarketplacePayload(action, context, installPath, c
|
|
|
58
145
|
if (sourceSnapshot.digest !== action.manifestDigest) {
|
|
59
146
|
throw new Error('frozen marketplace snapshot digest no longer matches the plan');
|
|
60
147
|
}
|
|
148
|
+
// Consumer marketplaces install only the plugin entry's declared source
|
|
149
|
+
// subtree (e.g. "./adapters/claude"), not the whole unit snapshot. The
|
|
150
|
+
// sealed whole-snapshot digest above remains the authority; bind the
|
|
151
|
+
// installed payload to that snapshot's declared subtree. Root layouts
|
|
152
|
+
// and kimi keep the whole-tree comparison ("." subpath, no filtering).
|
|
153
|
+
const payloadSubpath = await resolveInstalledPayloadSubpath(
|
|
154
|
+
sourcePath,
|
|
155
|
+
sourceSnapshot.entries,
|
|
156
|
+
action,
|
|
157
|
+
consumer,
|
|
158
|
+
);
|
|
159
|
+
const prefix = payloadSubpath === '.' ? null : `${payloadSubpath}/`;
|
|
160
|
+
const authorityEntries = prefix === null
|
|
161
|
+
? sourceSnapshot.entries
|
|
162
|
+
: sourceSnapshot.entries
|
|
163
|
+
// The trailing slash keeps sibling directories (e.g.
|
|
164
|
+
// "adapters/claude-x") out of the comparison set. Prefix removal on
|
|
165
|
+
// a path-sorted array is order-preserving, so no re-sort is needed.
|
|
166
|
+
.filter((entry) => entry.path.startsWith(prefix))
|
|
167
|
+
.map((entry) => ({ ...entry, path: entry.path.slice(prefix.length) }));
|
|
168
|
+
if (authorityEntries.length === 0) {
|
|
169
|
+
throw new Error('frozen snapshot contains no payload under the declared marketplace source');
|
|
170
|
+
}
|
|
61
171
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
62
|
-
excludeRootEntries: consumer
|
|
172
|
+
excludeRootEntries: consumerTransportExclusions(consumer),
|
|
63
173
|
});
|
|
64
174
|
if (
|
|
65
|
-
JSON.stringify(transportPayload(
|
|
175
|
+
JSON.stringify(transportPayload(authorityEntries))
|
|
66
176
|
!== JSON.stringify(transportPayload(installedSnapshot.entries))
|
|
67
177
|
) {
|
|
68
178
|
throw new Error('installed marketplace payload differs in path, bytes, size, or non-write mode bits');
|
|
@@ -1139,27 +1249,17 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
1139
1249
|
// Entry source must be a safe relative path within the snapshot.
|
|
1140
1250
|
// Accepts "./" (root-level), "./adapters/claude" (subdirectory),
|
|
1141
1251
|
// etc. Rejects absolute paths, ".." traversal, remote URLs, and
|
|
1142
|
-
// empty strings.
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1150
|
-
error: `marketplace plugin entry source must be a non-empty relative path${consumer === 'codex' ? ' (object with source:"local")' : ''}, got ${JSON.stringify(entry.source)}`,
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
if (
|
|
1154
|
-
sourcePath.startsWith('/') ||
|
|
1155
|
-
sourcePath.includes('..') ||
|
|
1156
|
-
sourcePath.includes('\\') ||
|
|
1157
|
-
/^https?:\/\//i.test(sourcePath)
|
|
1158
|
-
) {
|
|
1252
|
+
// empty strings. Normalized to "." for root layouts; the same
|
|
1253
|
+
// helper backs verify-side payload subtree resolution, so both
|
|
1254
|
+
// paths can never drift apart.
|
|
1255
|
+
let sourcePath;
|
|
1256
|
+
try {
|
|
1257
|
+
sourcePath = extractDeclaredPluginSource(consumer, entry);
|
|
1258
|
+
} catch (sourceErr) {
|
|
1159
1259
|
return createResult({
|
|
1160
1260
|
actionType,
|
|
1161
1261
|
status: ActionStatus.PREFLIGHT_FAILED,
|
|
1162
|
-
error:
|
|
1262
|
+
error: sourceErr.message,
|
|
1163
1263
|
});
|
|
1164
1264
|
}
|
|
1165
1265
|
// Verify the declared source directory exists and contains the
|
|
@@ -2290,7 +2390,7 @@ export function createPluginMarketplaceAdapter(deps = {}) {
|
|
|
2290
2390
|
// is returned and verify therefore fails closed.
|
|
2291
2391
|
try {
|
|
2292
2392
|
const installedSnapshot = await computeFrozenSnapshot(installPath, {
|
|
2293
|
-
excludeRootEntries: consumer
|
|
2393
|
+
excludeRootEntries: consumerTransportExclusions(consumer),
|
|
2294
2394
|
});
|
|
2295
2395
|
manifestDigest = installedSnapshot.digest;
|
|
2296
2396
|
} catch {
|
package/src/snapshot/frozen.mjs
CHANGED
|
@@ -82,9 +82,10 @@ async function readStableRegularFile(filePath, displayPath) {
|
|
|
82
82
|
* Compute the canonical snapshot digest.
|
|
83
83
|
*
|
|
84
84
|
* `excludeRootEntries` is reserved for consumer-owned transport metadata
|
|
85
|
-
* that is not part of the published payload
|
|
86
|
-
*
|
|
87
|
-
* all payload paths retain the
|
|
85
|
+
* that is not part of the published payload: Codex's root `.git` checkout
|
|
86
|
+
* metadata and Claude's root `.in_use` in-use plugin marker. Exclusions
|
|
87
|
+
* only apply to direct children of the root; all payload paths retain the
|
|
88
|
+
* normal fail-closed file checks.
|
|
88
89
|
*/
|
|
89
90
|
export async function computeFrozenSnapshot(snapshotDir, { excludeRootEntries = [] } = {}) {
|
|
90
91
|
const root = await realpath(snapshotDir);
|