@entrorove/skill 0.1.2 → 0.1.6
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/AGREEMENT.md +14 -14
- package/CHANGELOG.md +26 -0
- package/SKILL.md +6 -8
- package/cli.mjs +20 -11
- package/manifest.json +1 -1
- package/package.json +9 -3
- package/release-state.json +14 -0
- package/release.mjs +131 -0
- package/releases.json +32 -0
- package/version.mjs +8 -0
package/AGREEMENT.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# entrorove 使用、隐私与广告信息协议
|
|
2
2
|
|
|
3
|
-
版本:2026-09-29
|
|
3
|
+
版本:2026-09-29.2
|
|
4
4
|
|
|
5
|
-
本协议是首次使用
|
|
5
|
+
本协议是首次使用 entrorove 技能时的合并告知与使用条款。只有用户明确选择“同意”后,技能请求才会发送。IDE 的“完全访问”权限只是本地权限,不代表同意本协议。
|
|
6
6
|
|
|
7
7
|
## 1. 技能与权限
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- entrorove 会按用户与 IDE 明确提供的任务、上下文和权限辅助分析、修改代码及运行检查。
|
|
10
10
|
- 高权限操作可能修改或删除文件、运行命令或调用外部服务。用户应检查范围、保留备份,并对有重大后果的操作单独确认。
|
|
11
11
|
- 本协议不扩大 IDE 已授予的权限,也不替代具体高风险操作所需的单独确认。
|
|
12
12
|
|
|
13
13
|
## 2. 隐私与数据
|
|
14
14
|
|
|
15
|
-
- 安装包不会因安装本身自动扫描或上传项目文件。只有在发起技能调用时,才会向
|
|
15
|
+
- 安装包不会因安装本身自动扫描或上传项目文件。只有在发起技能调用时,才会向 entrorove API 发送 IDE 明确组装的请求负载。
|
|
16
16
|
- 账号服务会处理登录邮箱、密钥哈希与前缀、加密保存的完整密钥、会话与安全记录、调用时间/数量,以及用户自愿提交的留言。已登录用户可再次复制自己的完整密钥。
|
|
17
|
-
-
|
|
17
|
+
- 当前实现不将项目源文件或技能任务负载写入业务数据库,不出售个人信息。推广选择使用密钥的连续调用时长和调用次数判断展示时机,不分析项目内容或建立跨站画像。
|
|
18
18
|
- 本地同意回执仅保存协议版本、同意结果和时间,不包含邮箱、项目内容或密钥。
|
|
19
19
|
|
|
20
20
|
## 3. 消息与广告
|
|
21
21
|
|
|
22
22
|
- 在长时间任务的“请勿关闭”提示下,服务可按后台配置追加一条带 HTTPS 链接的消息。
|
|
23
23
|
- 商业内容以后台配置的分类标签显示为 `【分类】`,不伪装成执行结果、系统警告或独立推荐。
|
|
24
|
-
-
|
|
24
|
+
- 推广仅在配置的有效期和任务使用时段内展示。后台可设置候选权重与概率范围;服务根据同一密钥的连续调用时长及次数调整概率,不分析任务文本或项目文件。
|
|
25
25
|
- 广告链接指向第三方时,第三方的内容、隐私与条款由其自行负责。
|
|
26
26
|
|
|
27
27
|
## 4. 风险与责任限制
|
|
28
28
|
|
|
29
29
|
- 技能按现状提供,不保证所有结果均正确、完整、不中断或适合特定目的。用户应在合并、发布或用于生产前审查修改并运行适当检查。
|
|
30
|
-
- 在适用法律允许的最大范围内,
|
|
30
|
+
- 在适用法律允许的最大范围内,entrorove 不承担因使用或无法使用技能导致的间接、附带、特殊或后果性损失。
|
|
31
31
|
- 上述限制不排除法律不允许排除的责任,也不限制欺诈、故意不当行为或适用法律规定的强制性消费者权利。
|
|
32
32
|
|
|
33
33
|
## 5. 同意、拒绝与更新
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
#
|
|
41
|
+
# entrorove Terms, Privacy and Advertising Notice
|
|
42
42
|
|
|
43
|
-
Version: 2026-09-29
|
|
43
|
+
Version: 2026-09-29.2
|
|
44
44
|
|
|
45
45
|
This combined notice applies on first use. A skill request is sent only after an explicit **Accept** choice. Granting an IDE “full access” permission does not accept this agreement.
|
|
46
46
|
|
|
47
|
-
- **Use and access.**
|
|
48
|
-
- **Data.** Installation alone does not scan or upload project files. The account service processes login email, key hashes and prefixes, an encrypted copy of the full key that a signed-in user can copy again, sessions and security records, invocation time/count, and messages submitted voluntarily. Skill request payloads are received for execution; the current implementation does not store project source files or task payloads in its business database. It does not sell personal information
|
|
49
|
-
- **Advertising.** A configured message may appear beneath a long-running-task notice. Commercial content carries its configured category label as `【Category
|
|
50
|
-
- **Risk.** The skill is provided as available without a guarantee that every result is correct, complete, uninterrupted or fit for a particular purpose. Review and test changes before production use. To the maximum extent permitted by applicable law,
|
|
47
|
+
- **Use and access.** entrorove acts only on tasks, context and permissions supplied by the user and IDE. Elevated permissions may permit file changes, command execution and external calls. Review scope, keep backups and separately confirm consequential actions.
|
|
48
|
+
- **Data.** Installation alone does not scan or upload project files. The account service processes login email, key hashes and prefixes, an encrypted copy of the full key that a signed-in user can copy again, sessions and security records, invocation time/count, and messages submitted voluntarily. Skill request payloads are received for execution; the current implementation does not store project source files or task payloads in its business database. It does not sell personal information. Promotion timing uses consecutive invocation duration and count for the same key, without examining project content or building a cross-site profile.
|
|
49
|
+
- **Advertising.** A configured message may appear beneath a long-running-task notice. Commercial content carries its configured category label as `【Category】` and includes an HTTPS link. Administrators set active dates, task-duration windows, selection weights and probability bounds. The service adjusts display probability using consecutive invocation duration and count, without inspecting task text or project files. Third-party links are governed by their operators.
|
|
50
|
+
- **Risk.** The skill is provided as available without a guarantee that every result is correct, complete, uninterrupted or fit for a particular purpose. Review and test changes before production use. To the maximum extent permitted by applicable law, entrorove excludes indirect, incidental, special and consequential losses. Nothing excludes non-waivable liability, fraud, wilful misconduct or mandatory consumer rights.
|
|
51
51
|
- **Choice.** Declining prevents the skill request from being sent. Material changes require a new explicit acceptance. Mandatory local laws may provide additional rights. Qualified counsel should review this notice before production release for the actual operating and user jurisdictions.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.6 — 2026-09-29
|
|
4
|
+
|
|
5
|
+
- 配置密钥前检查凭据目录,拒绝符号链接目录,避免密钥写入非预期位置。
|
|
6
|
+
- Reject symlinked credential directories before writing a key to prevent storage at an unintended location.
|
|
7
|
+
|
|
8
|
+
## 0.1.5 — 2026-09-29
|
|
9
|
+
|
|
10
|
+
- 发布前自动检测安装包、技能说明与清单变更,递增版本并同步更新日志;凭据继续跨升级留存。
|
|
11
|
+
- Automatically detect package, skill instruction, and manifest changes before publishing, increment the version, and update release notes while retaining credentials across upgrades.
|
|
12
|
+
|
|
13
|
+
## 0.1.4 — 2026-09-29
|
|
14
|
+
|
|
15
|
+
- 品牌展示统一为小写 entrorove,保留原有密钥存储路径以确保升级不丢失凭据。
|
|
16
|
+
- Standardized the displayed brand as lowercase entrorove while preserving existing credential storage paths across upgrades.
|
|
17
|
+
|
|
18
|
+
## 0.1.3 — 2026-09-29
|
|
19
|
+
|
|
20
|
+
- 安装后密钥持久留存;升级时只保留一个受管技能并阻止旧版覆盖,同时明确说明按连续使用时长与调用次数调整推广概率。
|
|
21
|
+
- Keep the key across upgrades, preserve one managed skill and reject downgrades, and disclose usage-based promotion timing.
|
|
22
|
+
|
|
23
|
+
## 0.1.2 — 2026-09-29
|
|
24
|
+
|
|
25
|
+
- 已加入统一技能安装、持久化账号密钥和首次使用确认。
|
|
26
|
+
- Added the unified skill installer, persistent account key, and explicit first-use agreement.
|
package/SKILL.md
CHANGED
|
@@ -5,19 +5,19 @@ description: An engineering skill that understands a task, chooses a fitting wor
|
|
|
5
5
|
|
|
6
6
|
# entrorove · 熵巡
|
|
7
7
|
|
|
8
|
-
Version: 0.1.
|
|
8
|
+
Version: 0.1.6.
|
|
9
9
|
|
|
10
10
|
Read the user's goal and repository instructions first. Preserve the original task through planning, execution and verification. Use the workflow only where it helps; a small, low-risk edit can proceed directly. Never claim a reported step is a verified result.
|
|
11
11
|
|
|
12
|
-
Install or update the managed copy with `npx @entrorove/skill install`. The installer removes only pristine duplicate
|
|
12
|
+
Install or update the managed copy with `npx @entrorove/skill install`. The installer keeps one global managed entrorove skill, replaces its older pristine version, removes only pristine duplicate installations, and preserves hand-edited files. An older package cannot overwrite a newer managed version.
|
|
13
13
|
|
|
14
|
-
Register or sign in by email at https://entrorove.com. Enter the account key with `npx @entrorove/skill configure` and `{"token":"..."}` on standard input. Never place
|
|
14
|
+
Register or sign in by email at https://entrorove.com. Enter the account key once with `npx @entrorove/skill configure` and `{"token":"..."}` on standard input. The credential persists in the current OS user's restricted local store across skill upgrades. Before asking again, use the stored credential; a missing or invalid credential must be reported explicitly. Never place the key in arguments, URLs, project files or logs. entrorove accounts and keys are independent of other services.
|
|
15
15
|
|
|
16
|
-
On first use, the CLI returns a Confirm Protocol-compatible agreement interaction. Render it with the IDE's native confirmation UI, or show its `chatFallback` text when no adapter exists. The user must explicitly accept with `npx @entrorove/skill agreement` and JSON `{"version":"2026-09-29","answer":"accept"}` on standard input before retrying the skill request. A full-access IDE permission never counts as agreement acceptance. The combined terms, privacy and advertising notice is installed as `AGREEMENT.md`.
|
|
16
|
+
On first use or after a material agreement revision, the CLI returns a Confirm Protocol-compatible agreement interaction. Render it with the IDE's native confirmation UI, or show its `chatFallback` text when no adapter exists. The user must explicitly accept with `npx @entrorove/skill agreement` and JSON `{"version":"2026-09-29.2","answer":"accept"}` on standard input before retrying the skill request. A full-access IDE permission never counts as agreement acceptance. The combined terms, privacy and advertising notice is installed as `AGREEMENT.md`.
|
|
17
17
|
|
|
18
18
|
## Work with a task
|
|
19
19
|
|
|
20
|
-
Send JSON on standard input to `npx @entrorove/skill invoke`. Start with `operation: "start"` and an `input.task` containing the goal, work type, risk, difficulty and observed scope facts. Work types are `implementation`, `documentation`, `calculation`, `merge`, `review` and `decision`. Assess facts from the real task and repository; do not invent certainty.
|
|
20
|
+
Send JSON on standard input to `npx @entrorove/skill invoke`. Start with `operation: "start"` and an `input.task` containing the goal, work type, risk, difficulty and observed scope facts. Work types are `implementation`, `documentation`, `calculation`, `merge`, `review` and `decision`. Assess facts from the real task and repository; do not invent certainty. entrorove chooses the workflow. A small task may return `decision: "direct"`, meaning the IDE should do the work without another service call.
|
|
21
21
|
|
|
22
22
|
```json
|
|
23
23
|
{"operation":"start","input":{"task":{"goal":"Update the checkout flow and verify it","workType":"implementation","risk":"medium","difficulty":"medium","crossModule":true,"parallel":false,"unclear":false,"architectureContract":false}}}
|
|
@@ -27,6 +27,4 @@ For a returned `flowId`, continue with `{"operation":"continue","input":{"flowId
|
|
|
27
27
|
|
|
28
28
|
For a decision, include `input.task.interaction` with `type` (`confirm`, `choice`, `multi` or `input`), a `question`, `options` (two or more with unique `id` and `label`, or an empty array for input), and a `callback` containing its `operation` and `payload`. Medium- and high-risk decisions also need a `riskDescription`. The decision waits for an explicit answer; it never chooses a default on timeout.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
When the user gives an actual evaluation after using the skill, offer to publish that feedback. Only after they explicitly agree to public display, send their own words, IDE name and 1–5 rating as JSON on standard input to `npx @entrorove/skill review` with `publicConsent: true`. Never invent, rewrite or infer a review from task completion. The website reads accepted reviews directly from Entrorove's independent database.
|
|
30
|
+
When the user gives an actual evaluation after using the skill, offer to publish that feedback. Only after they explicitly agree to public display, send their own words, IDE name and 1–5 rating as JSON on standard input to `npx @entrorove/skill review` with `publicConsent: true`. Never invent, rewrite or infer a review from task completion. The website reads accepted reviews directly from entrorove's independent database.
|
package/cli.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { promises as fs, realpathSync } from 'node:fs';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { compareVersions } from './version.mjs';
|
|
8
9
|
|
|
9
10
|
const packageDir = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const apiUrl = 'https://entrorove.com/api/skill/invoke';
|
|
@@ -17,7 +18,7 @@ const agreementSchema = 'entrorove.agreement-acceptance/1.0';
|
|
|
17
18
|
const invocationTimeoutMs = 135_000;
|
|
18
19
|
const installLockWaitMs = 10_000;
|
|
19
20
|
const installLockStaleMs = 120_000;
|
|
20
|
-
export const agreementVersion = '2026-09-29';
|
|
21
|
+
export const agreementVersion = '2026-09-29.2';
|
|
21
22
|
|
|
22
23
|
function sha256(content) {
|
|
23
24
|
return createHash('sha256').update(content).digest('hex');
|
|
@@ -48,7 +49,7 @@ async function sourceBundle() {
|
|
|
48
49
|
content.set(name, await fs.readFile(path.join(packageDir, name)));
|
|
49
50
|
}
|
|
50
51
|
if (!content.get('SKILL.md').toString('utf8').includes(`Version: ${manifest.version}.`)) {
|
|
51
|
-
throw new Error('
|
|
52
|
+
throw new Error('entrorove skill instructions have a mismatched version');
|
|
52
53
|
}
|
|
53
54
|
const hashes = Object.fromEntries(installFiles.map((name) => [name, sha256(content.get(name))]));
|
|
54
55
|
return { manifest, content, hashes };
|
|
@@ -79,7 +80,7 @@ export function agreementInteraction() {
|
|
|
79
80
|
schemaVersion: 'confirm.interaction/1.0',
|
|
80
81
|
requestId: `entrorove-agreement-${agreementVersion}`,
|
|
81
82
|
type: 'confirm',
|
|
82
|
-
question: `首次使用前,请阅读
|
|
83
|
+
question: `首次使用前,请阅读 entrorove 使用、隐私与广告信息协议(${agreementVersion})。是否同意?`,
|
|
83
84
|
options: [
|
|
84
85
|
{ id: 'accept', label: '同意', hint: '记录本地回执并允许后续技能调用' },
|
|
85
86
|
{ id: 'decline', label: '不同意', hint: '不发送技能请求' },
|
|
@@ -104,7 +105,7 @@ function agreementRequired(home) {
|
|
|
104
105
|
agreement,
|
|
105
106
|
interaction: agreementInteraction(),
|
|
106
107
|
chatFallback: [
|
|
107
|
-
`
|
|
108
|
+
`entrorove 首次使用确认(${agreementVersion})`,
|
|
108
109
|
`请先阅读:${agreement}`,
|
|
109
110
|
'1. accept — 同意并继续',
|
|
110
111
|
'2. decline — 不同意,不发送请求',
|
|
@@ -168,7 +169,7 @@ async function removeStaleInstallLock(lock) {
|
|
|
168
169
|
const state = await statOrNull(lock);
|
|
169
170
|
if (!state) return true;
|
|
170
171
|
if (!state.isDirectory() || state.isSymbolicLink()) {
|
|
171
|
-
throw new Error('
|
|
172
|
+
throw new Error('entrorove install lock is not a directory');
|
|
172
173
|
}
|
|
173
174
|
if (Date.now() - state.mtimeMs < installLockStaleMs) return false;
|
|
174
175
|
let owner;
|
|
@@ -195,7 +196,7 @@ async function removeStaleInstallLock(lock) {
|
|
|
195
196
|
const moved = await fs.lstat(quarantine);
|
|
196
197
|
if (moved.ino !== state.ino || moved.dev !== state.dev) {
|
|
197
198
|
await fs.rename(quarantine, lock);
|
|
198
|
-
throw new Error('
|
|
199
|
+
throw new Error('entrorove install lock changed during stale recovery');
|
|
199
200
|
}
|
|
200
201
|
await fs.rm(quarantine, { recursive: true });
|
|
201
202
|
return true;
|
|
@@ -211,7 +212,7 @@ async function withInstallLock(parent, action) {
|
|
|
211
212
|
} catch (error) {
|
|
212
213
|
if (error.code !== 'EEXIST') throw error;
|
|
213
214
|
if (await removeStaleInstallLock(lock)) continue;
|
|
214
|
-
if (Date.now() >= deadline) throw new Error('
|
|
215
|
+
if (Date.now() >= deadline) throw new Error('entrorove install is busy');
|
|
215
216
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
216
217
|
}
|
|
217
218
|
}
|
|
@@ -229,6 +230,9 @@ async function updateGlobalInstall(target, bundle) {
|
|
|
229
230
|
await fs.mkdir(parent, { recursive: true, mode: 0o700 });
|
|
230
231
|
const previous = await managedState(target);
|
|
231
232
|
if (previous === false) throw new Error('Global entrorove skill is not pristine and managed; refusing overwrite');
|
|
233
|
+
if (previous && compareVersions(previous.version, bundle.manifest.version) > 0) {
|
|
234
|
+
throw new Error('A newer managed entrorove skill is installed; use the latest package');
|
|
235
|
+
}
|
|
232
236
|
if (previous?.version === bundle.manifest.version
|
|
233
237
|
&& installFiles.every((name) => previous.files[name] === bundle.hashes[name])) return false;
|
|
234
238
|
const stage = path.join(parent, `.entrorove-stage-${randomUUID()}`);
|
|
@@ -314,14 +318,20 @@ async function stdinJson() {
|
|
|
314
318
|
return value;
|
|
315
319
|
}
|
|
316
320
|
|
|
321
|
+
async function preparePrivateDirectory(parent) {
|
|
322
|
+
await fs.mkdir(parent, { recursive: true, mode: 0o700 });
|
|
323
|
+
const state = await fs.lstat(parent);
|
|
324
|
+
if (!state.isDirectory() || state.isSymbolicLink()) throw new Error('Credential directory must be a real directory');
|
|
325
|
+
if (process.platform !== 'win32') await fs.chmod(parent, 0o700);
|
|
326
|
+
}
|
|
327
|
+
|
|
317
328
|
async function writeCredential(token, home) {
|
|
318
329
|
if (typeof token !== 'string' || !/^ent_[A-Za-z0-9_-]{16,256}$/.test(token)) {
|
|
319
330
|
throw new Error('Invalid entrorove key');
|
|
320
331
|
}
|
|
321
332
|
const file = credentialPath(home);
|
|
322
333
|
const parent = path.dirname(file);
|
|
323
|
-
await
|
|
324
|
-
if (process.platform !== 'win32') await fs.chmod(parent, 0o700);
|
|
334
|
+
await preparePrivateDirectory(parent);
|
|
325
335
|
const existing = await statOrNull(file);
|
|
326
336
|
if (existing && (!existing.isFile() || existing.isSymbolicLink())) throw new Error('Credential path is not a regular file');
|
|
327
337
|
const stage = path.join(parent, `.credential-${randomUUID()}.tmp`);
|
|
@@ -369,8 +379,7 @@ async function writeAgreementAcceptance(data, home) {
|
|
|
369
379
|
if (data.answer === 'decline') return false;
|
|
370
380
|
const file = agreementPath(home);
|
|
371
381
|
const parent = path.dirname(file);
|
|
372
|
-
await
|
|
373
|
-
if (process.platform !== 'win32') await fs.chmod(parent, 0o700);
|
|
382
|
+
await preparePrivateDirectory(parent);
|
|
374
383
|
const existing = await statOrNull(file);
|
|
375
384
|
if (existing && (!existing.isFile() || existing.isSymbolicLink())) {
|
|
376
385
|
throw new Error('Agreement path is not a regular file');
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"entrorove": "cli.mjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"cli.mjs",
|
|
10
|
+
"version.mjs",
|
|
10
11
|
"SKILL.md",
|
|
11
12
|
"AGREEMENT.md",
|
|
12
|
-
"manifest.json"
|
|
13
|
+
"manifest.json",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"releases.json",
|
|
16
|
+
"release.mjs",
|
|
17
|
+
"release-state.json"
|
|
13
18
|
],
|
|
14
19
|
"scripts": {
|
|
15
|
-
"test": "node --test test/*.test.mjs"
|
|
20
|
+
"test": "node --test test/*.test.mjs",
|
|
21
|
+
"prepublishOnly": "node release.mjs"
|
|
16
22
|
},
|
|
17
23
|
"engines": {
|
|
18
24
|
"node": ">=20"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"digest": "4ab58749501fc5c9e3fe609b28142ea5840eea737c26d709b775a463edf9df47",
|
|
5
|
+
"files": {
|
|
6
|
+
"cli.mjs": "35e9f546dc8750d034c4804030361388ea81551366494e9d16a2b65cc5c75fcb",
|
|
7
|
+
"version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
|
|
8
|
+
"SKILL.md": "3aa9031fe5df2989096d4153717a439d646d30b8ab03d2df2f0207731b1e400a",
|
|
9
|
+
"AGREEMENT.md": "24a38ad19b8364a7fafed145782fb91469bbf3bb018f0c845b02c4dc2a534f4e",
|
|
10
|
+
"package.json": "a63df90309025a4fca247b3236c4602d81cea6ca3c52b6e5a2acfafc8c90dfe9",
|
|
11
|
+
"manifest.json": "a2086fe3e4dded88775ef7d9cd978e958dc6c8d42f16e7f559d77562ef5c6f59",
|
|
12
|
+
"release.mjs": "7db31862735e8e734207cbba05ac2a5f58e2faf9b4c752100a6af098f9eaf7eb"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/release.mjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readFile, writeFile, rename, rm } from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const directory = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const sourceFiles = [
|
|
8
|
+
'cli.mjs', 'version.mjs', 'SKILL.md', 'AGREEMENT.md',
|
|
9
|
+
'package.json', 'manifest.json', 'release.mjs',
|
|
10
|
+
]
|
|
11
|
+
const stateFile = 'release-state.json'
|
|
12
|
+
const changelogFile = 'CHANGELOG.md'
|
|
13
|
+
const releasesFile = 'releases.json'
|
|
14
|
+
|
|
15
|
+
async function read(name) {
|
|
16
|
+
return readFile(path.join(directory, name), 'utf8')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function digest(value) {
|
|
20
|
+
return createHash('sha256').update(value).digest('hex')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalized(name, text) {
|
|
24
|
+
if (name === 'SKILL.md') {
|
|
25
|
+
return text.replace(/^Version: \d+\.\d+\.\d+\.$/m, 'Version: <release>.')
|
|
26
|
+
}
|
|
27
|
+
if (name === 'package.json' || name === 'manifest.json') {
|
|
28
|
+
const metadata = JSON.parse(text)
|
|
29
|
+
metadata.version = '<release>'
|
|
30
|
+
return JSON.stringify(metadata)
|
|
31
|
+
}
|
|
32
|
+
return text
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function sourceDigests() {
|
|
36
|
+
const entries = await Promise.all(sourceFiles.map(async name =>
|
|
37
|
+
[name, digest(normalized(name, await read(name)))]))
|
|
38
|
+
return Object.fromEntries(entries)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function versionNext(version) {
|
|
42
|
+
const parts = version.split('.').map(Number)
|
|
43
|
+
if (parts.length !== 3 || parts.some(part => !Number.isSafeInteger(part) || part < 0)) {
|
|
44
|
+
throw new Error('Skill version is not semantic version MAJOR.MINOR.PATCH')
|
|
45
|
+
}
|
|
46
|
+
return `${parts[0]}.${parts[1]}.${parts[2] + 1}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function releaseNote(changed) {
|
|
50
|
+
const suppliedZh = process.env.ENTROROVE_RELEASE_NOTE_ZH?.trim()
|
|
51
|
+
const suppliedEn = process.env.ENTROROVE_RELEASE_NOTE_EN?.trim()
|
|
52
|
+
if (Boolean(suppliedZh) !== Boolean(suppliedEn)) {
|
|
53
|
+
throw new Error('Release notes must provide both Chinese and English')
|
|
54
|
+
}
|
|
55
|
+
if (suppliedZh && suppliedEn) return {
|
|
56
|
+
zh: suppliedZh.replaceAll(/\s+/g, ' '), en: suppliedEn.replaceAll(/\s+/g, ' '),
|
|
57
|
+
}
|
|
58
|
+
const labels = {
|
|
59
|
+
'cli.mjs': { zh: '技能运行和受管安装', en: 'skill runtime and managed installation' },
|
|
60
|
+
'version.mjs': { zh: '版本安全检查', en: 'version safety checks' },
|
|
61
|
+
'SKILL.md': { zh: 'IDE 工作流程说明', en: 'IDE workflow instructions' },
|
|
62
|
+
'AGREEMENT.md': { zh: '使用、隐私与推广告知', en: 'usage, privacy, and promotion notice' },
|
|
63
|
+
'package.json': { zh: '安装包配置', en: 'package configuration' },
|
|
64
|
+
'manifest.json': { zh: '技能清单', en: 'skill manifest' },
|
|
65
|
+
'release.mjs': { zh: '自动版本与更新日志', en: 'automatic versioning and changelog' },
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
zh: `更新了${changed.map(name => labels[name].zh).join('、')}。`,
|
|
69
|
+
en: `Updated ${changed.map(name => labels[name].en).join(', ')}.`,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function writeAll(changes) {
|
|
74
|
+
const originals = new Map()
|
|
75
|
+
const staged = []
|
|
76
|
+
try {
|
|
77
|
+
for (const [name, content] of changes) {
|
|
78
|
+
originals.set(name, await read(name))
|
|
79
|
+
const temporary = path.join(directory, `.${name}.${process.pid}.tmp`)
|
|
80
|
+
await writeFile(temporary, content, { flag: 'wx', mode: 0o600 })
|
|
81
|
+
staged.push([name, temporary])
|
|
82
|
+
}
|
|
83
|
+
for (const [name, temporary] of staged) await rename(temporary, path.join(directory, name))
|
|
84
|
+
} catch (error) {
|
|
85
|
+
for (const [name, content] of originals) await writeFile(path.join(directory, name), content)
|
|
86
|
+
for (const [, temporary] of staged) await rm(temporary, { force: true })
|
|
87
|
+
throw error
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const metadata = JSON.parse(await read('package.json'))
|
|
92
|
+
const manifest = JSON.parse(await read('manifest.json'))
|
|
93
|
+
const state = JSON.parse(await read(stateFile))
|
|
94
|
+
const skill = await read('SKILL.md')
|
|
95
|
+
if (metadata.version !== manifest.version || metadata.version !== state.version
|
|
96
|
+
|| !skill.includes(`Version: ${metadata.version}.`)) {
|
|
97
|
+
throw new Error('Skill package, manifest, instruction, and release state versions differ')
|
|
98
|
+
}
|
|
99
|
+
const files = await sourceDigests()
|
|
100
|
+
const currentDigest = digest(JSON.stringify(files))
|
|
101
|
+
if (currentDigest !== state.digest) {
|
|
102
|
+
const changed = sourceFiles.filter(name => files[name] !== state.files[name])
|
|
103
|
+
if (changed.length === 0) throw new Error('Skill source digest changed without a changed file')
|
|
104
|
+
const next = versionNext(metadata.version)
|
|
105
|
+
metadata.version = next
|
|
106
|
+
manifest.version = next
|
|
107
|
+
const nextSkill = skill.replace(`Version: ${state.version}.`, `Version: ${next}.`)
|
|
108
|
+
const changelog = await read(changelogFile)
|
|
109
|
+
const date = new Date().toISOString().slice(0, 10)
|
|
110
|
+
const notes = releaseNote(changed)
|
|
111
|
+
const entry = `## ${next} — ${date}\n\n- ${notes.zh}\n- ${notes.en}\n\n`
|
|
112
|
+
const nextLog = changelog.replace('# Changelog\n\n', `# Changelog\n\n${entry}`)
|
|
113
|
+
if (nextLog === changelog) throw new Error('Changelog header is missing')
|
|
114
|
+
const releases = JSON.parse(await read(releasesFile))
|
|
115
|
+
if (!Array.isArray(releases) || releases[0]?.version !== state.version) {
|
|
116
|
+
throw new Error('Release index does not match current skill version')
|
|
117
|
+
}
|
|
118
|
+
const nextState = { schemaVersion: 'entrorove.release-state/1.0', version: next,
|
|
119
|
+
digest: currentDigest, files }
|
|
120
|
+
await writeAll([
|
|
121
|
+
['package.json', JSON.stringify(metadata, null, 2) + '\n'],
|
|
122
|
+
['manifest.json', JSON.stringify(manifest, null, 2) + '\n'],
|
|
123
|
+
['SKILL.md', nextSkill],
|
|
124
|
+
[changelogFile, nextLog],
|
|
125
|
+
[releasesFile, JSON.stringify([{ version: next, date, ...notes }, ...releases], null, 2) + '\n'],
|
|
126
|
+
[stateFile, JSON.stringify(nextState, null, 2) + '\n'],
|
|
127
|
+
])
|
|
128
|
+
process.stdout.write(`entrorove skill ${next}: ${notes.en}\n`)
|
|
129
|
+
} else {
|
|
130
|
+
process.stdout.write(`entrorove skill ${state.version}: no source change\n`)
|
|
131
|
+
}
|
package/releases.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"date": "2026-09-29",
|
|
5
|
+
"zh": "配置密钥前检查凭据目录,拒绝符号链接目录,避免密钥写入非预期位置。",
|
|
6
|
+
"en": "Reject symlinked credential directories before writing a key to prevent storage at an unintended location."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.5",
|
|
10
|
+
"date": "2026-09-29",
|
|
11
|
+
"zh": "发布前自动检测安装包、技能说明与清单变更,递增版本并同步更新日志;凭据继续跨升级留存。",
|
|
12
|
+
"en": "Automatically detect package, skill instruction, and manifest changes before publishing, increment the version, and update release notes while retaining credentials across upgrades."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.1.4",
|
|
16
|
+
"date": "2026-09-29",
|
|
17
|
+
"zh": "品牌展示统一为小写 entrorove,保留原有密钥存储路径以确保升级不丢失凭据。",
|
|
18
|
+
"en": "Standardized the displayed brand as lowercase entrorove while preserving existing credential storage paths across upgrades."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.1.3",
|
|
22
|
+
"date": "2026-09-29",
|
|
23
|
+
"zh": "安装后密钥持久留存;升级时只保留一个受管技能并阻止旧版覆盖,同时明确说明按连续使用时长与调用次数调整推广概率。",
|
|
24
|
+
"en": "Keep the key across upgrades, preserve one managed skill and reject downgrades, and disclose usage-based promotion timing."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.1.2",
|
|
28
|
+
"date": "2026-09-29",
|
|
29
|
+
"zh": "已加入统一技能安装、持久化账号密钥和首次使用确认。",
|
|
30
|
+
"en": "Added the unified skill installer, persistent account key, and explicit first-use agreement."
|
|
31
|
+
}
|
|
32
|
+
]
|
package/version.mjs
ADDED