1 Commits

Author SHA1 Message Date
dependabot[bot]
d674d9cff7 build(deps-dev): bump @opencode-ai/plugin in the minor-and-patch group
Bumps the minor-and-patch group with 1 update: @opencode-ai/plugin.


Updates `@opencode-ai/plugin` from 1.3.15 to 1.4.3

---
updated-dependencies:
- dependency-name: "@opencode-ai/plugin"
  dependency-version: 1.4.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-12 04:53:10 +00:00
16 changed files with 77 additions and 620 deletions

View File

@@ -6,7 +6,6 @@
"plugins": [
{
"name": "ecc",
"version": "1.10.0",
"source": {
"source": "local",
"path": "../.."

View File

@@ -38,21 +38,18 @@ jobs:
env:
REF_NAME: ${{ github.ref_name }}
- name: Verify package version matches tag
- name: Verify plugin.json version matches tag
env:
TAG_NAME: ${{ github.ref_name }}
run: |
TAG_VERSION="${TAG_NAME#v}"
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
PLUGIN_VERSION=$(grep -oE '"version": *"[^"]*"' .claude-plugin/plugin.json | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
if [ "$TAG_VERSION" != "$PLUGIN_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match plugin.json version ($PLUGIN_VERSION)"
echo "Run: ./scripts/release.sh $TAG_VERSION"
exit 1
fi
- name: Verify release metadata stays in sync
run: node tests/plugin-manifest.test.js
- name: Generate release highlights
id: highlights
env:

View File

@@ -47,21 +47,6 @@ jobs:
exit 1
fi
- name: Verify package version matches tag
env:
INPUT_TAG: ${{ inputs.tag }}
run: |
TAG_VERSION="${INPUT_TAG#v}"
PACKAGE_VERSION=$(node -p "require('./package.json').version")
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)"
echo "Run: ./scripts/release.sh $TAG_VERSION"
exit 1
fi
- name: Verify release metadata stays in sync
run: node tests/plugin-manifest.test.js
- name: Generate release highlights
env:
TAG_NAME: ${{ inputs.tag }}

View File

@@ -78,17 +78,6 @@
---
## 最新动态
### v1.10.0 — 表面同步、运营工作流与 ECC 2.0 Alpha2026年4月
- **公共表面已与真实仓库同步** —— 元数据、目录数量、插件清单以及安装文档现在都与实际开源表面保持一致。
- **运营与外向型工作流扩展** —— `brand-voice``social-graph-ranker``customer-billing-ops``google-workspace-ops` 等运营型 skill 已纳入同一系统。
- **媒体与发布工具补齐** —— `manim-video``remotion-video-creation` 以及社媒发布能力让技术讲解和发布流程直接在同一仓库内完成。
- **框架与产品表面继续扩展** —— `nestjs-patterns`、更完整的 Codex/OpenCode 安装表面,以及跨 harness 打包改进,让仓库不再局限于 Claude Code。
- **ECC 2.0 alpha 已进入仓库** —— `ecc2/` 下的 Rust 控制层现已可在本地构建,并提供 `dashboard``start``sessions``status``stop``resume``daemon` 命令。
- **生态加固持续推进** —— AgentShield、ECC Tools 成本控制、计费门户工作与网站刷新仍围绕核心插件持续交付。
## 快速开始
在 2 分钟内快速上手:

View File

@@ -80,15 +80,6 @@ Este repositório contém apenas o código. Os guias explicam tudo.
## O Que Há de Novo
### v1.10.0 — Sincronização de Superfície, Fluxos Operacionais e ECC 2.0 Alpha (Abr 2026)
- **Superfície pública sincronizada com o repositório real** — metadados, contagens de catálogo, manifests de plugin e documentação de instalação agora refletem a superfície OSS que realmente é entregue.
- **Expansão dos fluxos operacionais e externos** — `brand-voice`, `social-graph-ranker`, `customer-billing-ops`, `google-workspace-ops` e skills relacionadas fortalecem a trilha operacional dentro do mesmo sistema.
- **Ferramentas de mídia e lançamento** — `manim-video`, `remotion-video-creation` e os fluxos de publicação social colocam explicadores técnicos e lançamento no mesmo repositório.
- **Crescimento de framework e superfície de produto** — `nestjs-patterns`, superfícies de instalação mais ricas para Codex/OpenCode e melhorias de empacotamento cross-harness ampliam o uso além do Claude Code.
- **ECC 2.0 alpha já está no repositório** — o plano de controle em Rust dentro de `ecc2/` já compila localmente e expõe `dashboard`, `start`, `sessions`, `status`, `stop`, `resume` e `daemon`.
- **Fortalecimento do ecossistema** — AgentShield, controles de custo do ECC Tools, trabalho no portal de billing e a renovação do site continuam sendo entregues ao redor do plugin principal.
### v1.9.0 — Instalação Seletiva e Expansão de Idiomas (Mar 2026)
- **Arquitetura de instalação seletiva** — Pipeline de instalação baseado em manifesto com `install-plan.js` e `install-apply.js` para instalação de componentes direcionada. O state store rastreia o que está instalado e habilita atualizações incrementais.

View File

@@ -79,15 +79,6 @@ Bu repository yalnızca ham kodu içerir. Rehberler her şeyi açıklıyor.
## Yenilikler
### v1.10.0 — Surface Sync, Operatör İş Akışları ve ECC 2.0 Alpha (Nis 2026)
- **Public surface canlı repo ile senkronlandı** — metadata, katalog sayıları, plugin manifest'leri ve kurulum odaklı dokümanlar artık gerçek OSS yüzeyiyle eşleşiyor.
- **Operatör ve dışa dönük iş akışları büyüdü** — `brand-voice`, `social-graph-ranker`, `customer-billing-ops`, `google-workspace-ops` ve ilgili operatör skill'leri aynı sistem içinde tamamlandı.
- **Medya ve lansman araçları** — `manim-video`, `remotion-video-creation` ve sosyal yayın yüzeyleri teknik anlatım ve duyuru akışlarını aynı repo içine taşıdı.
- **Framework ve ürün yüzeyi genişledi** — `nestjs-patterns`, daha zengin Codex/OpenCode kurulum yüzeyleri ve çapraz harness paketleme iyileştirmeleri repo'yu Claude Code dışına da taşıdı.
- **ECC 2.0 alpha repoda** — `ecc2/` altındaki Rust kontrol katmanı artık yerelde derleniyor ve `dashboard`, `start`, `sessions`, `status`, `stop`, `resume` ve `daemon` komutlarını sunuyor.
- **Ekosistem sağlamlaştırma** — AgentShield, ECC Tools maliyet kontrolleri, billing portal işleri ve web yüzeyi çekirdek plugin etrafında birlikte gelişmeye devam ediyor.
### v1.9.0 — Seçici Kurulum & Dil Genişlemesi (Mar 2026)
- **Seçici kurulum mimarisi** — `install-plan.js` ve `install-apply.js` ile manifest-tabanlı kurulum pipeline'ı, hedefli component kurulumu için. State store neyin kurulu olduğunu takip eder ve artımlı güncellemelere olanak sağlar.

View File

@@ -50,7 +50,6 @@
".opencode/index.ts",
".opencode/opencode.json",
".opencode/package.json",
".opencode/package-lock.json",
".opencode/tsconfig.json",
".opencode/MIGRATION.md",
".opencode/README.md",
@@ -91,17 +90,14 @@
"scripts/uninstall.js",
"skills/",
"AGENTS.md",
"agent.yaml",
".claude-plugin/plugin.json",
".claude-plugin/marketplace.json",
".claude-plugin/README.md",
".codex-plugin/plugin.json",
".codex-plugin/README.md",
".mcp.json",
"install.sh",
"install.ps1",
"llms.txt",
"VERSION"
"llms.txt"
],
"bin": {
"ecc": "scripts/ecc.js",
@@ -129,7 +125,7 @@
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@opencode-ai/plugin": "^1.0.0",
"@opencode-ai/plugin": "^1.4.3",
"@types/node": "^20.19.24",
"c8": "^11.0.0",
"eslint": "^9.39.2",

View File

@@ -359,72 +359,44 @@ function gitRepoRoot(cwd) {
return runGit(['rev-parse', '--show-toplevel'], cwd);
}
const MAX_RELEVANT_PATCH_LINES = 6;
function candidateGitPaths(repoRoot, filePath) {
const resolvedRepoRoot = path.resolve(repoRoot);
const candidates = [];
const pushCandidate = value => {
const candidate = String(value || '').trim();
if (!candidate || candidates.includes(candidate)) {
return;
}
candidates.push(candidate);
};
const absoluteCandidates = path.isAbsolute(filePath)
? [path.resolve(filePath)]
: [
path.resolve(resolvedRepoRoot, filePath),
path.resolve(process.cwd(), filePath),
];
for (const absolute of absoluteCandidates) {
const relative = path.relative(resolvedRepoRoot, absolute);
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
continue;
}
pushCandidate(relative);
pushCandidate(relative.split(path.sep).join('/'));
pushCandidate(absolute);
pushCandidate(absolute.split(path.sep).join('/'));
function repoRelativePath(repoRoot, filePath) {
const absolute = path.isAbsolute(filePath)
? path.resolve(filePath)
: path.resolve(process.cwd(), filePath);
const relative = path.relative(repoRoot, absolute);
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
return null;
}
return candidates;
return relative.split(path.sep).join('/');
}
function patchPreviewFromGitDiff(repoRoot, pathCandidates) {
for (const candidate of pathCandidates) {
const patch = runGit(
['diff', '--no-ext-diff', '--no-color', '--unified=1', '--', candidate],
repoRoot
);
if (!patch) {
continue;
}
const relevant = patch
.split(/\r?\n/)
.filter(line =>
line.startsWith('@@')
|| (line.startsWith('+') && !line.startsWith('+++'))
|| (line.startsWith('-') && !line.startsWith('---'))
)
.slice(0, MAX_RELEVANT_PATCH_LINES);
if (relevant.length > 0) {
return relevant.join('\n');
}
}
return undefined;
}
function trackedInGit(repoRoot, pathCandidates) {
return pathCandidates.some(candidate =>
runGit(['ls-files', '--error-unmatch', '--', candidate], repoRoot) !== null
function patchPreviewFromGitDiff(repoRoot, repoRelative) {
const patch = runGit(
['diff', '--no-ext-diff', '--no-color', '--unified=1', '--', repoRelative],
repoRoot
);
if (!patch) {
return undefined;
}
const relevant = patch
.split(/\r?\n/)
.filter(line =>
line.startsWith('@@')
|| (line.startsWith('+') && !line.startsWith('+++'))
|| (line.startsWith('-') && !line.startsWith('---'))
)
.slice(0, 6);
if (relevant.length === 0) {
return undefined;
}
return relevant.join('\n');
}
function trackedInGit(repoRoot, repoRelative) {
return runGit(['ls-files', '--error-unmatch', '--', repoRelative], repoRoot) !== null;
}
function enrichFileEventFromWorkingTree(toolName, event) {
@@ -437,14 +409,14 @@ function enrichFileEventFromWorkingTree(toolName, event) {
return event;
}
const pathCandidates = candidateGitPaths(repoRoot, event.path);
if (pathCandidates.length === 0) {
const repoRelative = repoRelativePath(repoRoot, event.path);
if (!repoRelative) {
return event;
}
const tool = String(toolName || '').trim().toLowerCase();
const tracked = trackedInGit(repoRoot, pathCandidates);
const patchPreview = patchPreviewFromGitDiff(repoRoot, pathCandidates) || event.patch_preview;
const tracked = trackedInGit(repoRoot, repoRelative);
const patchPreview = patchPreviewFromGitDiff(repoRoot, repoRelative) || event.patch_preview;
const diffPreview = buildDiffPreviewFromPatchPreview(patchPreview) || event.diff_preview;
if (tool.includes('write')) {

View File

@@ -6,21 +6,9 @@ set -euo pipefail
VERSION="${1:-}"
ROOT_PACKAGE_JSON="package.json"
PACKAGE_LOCK_JSON="package-lock.json"
ROOT_AGENTS_MD="AGENTS.md"
TR_AGENTS_MD="docs/tr/AGENTS.md"
ZH_CN_AGENTS_MD="docs/zh-CN/AGENTS.md"
AGENT_YAML="agent.yaml"
VERSION_FILE="VERSION"
PLUGIN_JSON=".claude-plugin/plugin.json"
MARKETPLACE_JSON=".claude-plugin/marketplace.json"
CODEX_MARKETPLACE_JSON=".agents/plugins/marketplace.json"
CODEX_PLUGIN_JSON=".codex-plugin/plugin.json"
OPENCODE_PACKAGE_JSON=".opencode/package.json"
OPENCODE_PACKAGE_LOCK_JSON=".opencode/package-lock.json"
README_FILE="README.md"
ZH_CN_README_FILE="docs/zh-CN/README.md"
SELECTIVE_INSTALL_ARCHITECTURE_DOC="docs/SELECTIVE-INSTALL-ARCHITECTURE.md"
# Function to show usage
usage() {
@@ -48,14 +36,14 @@ if [[ "$CURRENT_BRANCH" != "main" ]]; then
exit 1
fi
# Check working tree is clean, including untracked files
if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then
# Check working tree is clean
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "Error: Working tree is not clean. Commit or stash changes first."
exit 1
fi
# Verify versioned manifests exist
for FILE in "$ROOT_PACKAGE_JSON" "$PACKAGE_LOCK_JSON" "$ROOT_AGENTS_MD" "$TR_AGENTS_MD" "$ZH_CN_AGENTS_MD" "$AGENT_YAML" "$VERSION_FILE" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$CODEX_MARKETPLACE_JSON" "$CODEX_PLUGIN_JSON" "$OPENCODE_PACKAGE_JSON" "$OPENCODE_PACKAGE_LOCK_JSON" "$README_FILE" "$ZH_CN_README_FILE" "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"; do
for FILE in "$ROOT_PACKAGE_JSON" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$OPENCODE_PACKAGE_JSON"; do
if [[ ! -f "$FILE" ]]; then
echo "Error: $FILE not found"
exit 1
@@ -70,6 +58,13 @@ if [[ -z "$OLD_VERSION" ]]; then
fi
echo "Bumping version: $OLD_VERSION -> $VERSION"
# Build and verify the packaged OpenCode payload before mutating any manifest
# versions or creating a tag. This keeps a broken npm artifact from being
# released via the manual script path.
echo "Verifying OpenCode build and npm pack payload..."
node scripts/build-opencode.js
node tests/scripts/build-opencode.test.js
update_version() {
local file="$1"
local pattern="$2"
@@ -80,170 +75,14 @@ update_version() {
fi
}
update_package_lock_version() {
node -e '
const fs = require("fs");
const file = process.argv[1];
const version = process.argv[2];
const lock = JSON.parse(fs.readFileSync(file, "utf8"));
if (!lock || typeof lock !== "object") {
console.error(`Error: ${file} does not contain a JSON object`);
process.exit(1);
}
lock.version = version;
if (!lock.packages || typeof lock.packages !== "object" || Array.isArray(lock.packages)) {
console.error(`Error: ${file} is missing lock.packages`);
process.exit(1);
}
if (!lock.packages[""] || typeof lock.packages[""] !== "object" || Array.isArray(lock.packages[""])) {
console.error(`Error: ${file} is missing lock.packages[\"\"]`);
process.exit(1);
}
lock.packages[""].version = version;
fs.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`);
' "$1" "$VERSION"
}
update_readme_version_row() {
local file="$1"
local label="$2"
local first_col="$3"
local second_col="$4"
local third_col="$5"
node -e '
const fs = require("fs");
const file = process.argv[1];
const version = process.argv[2];
const label = process.argv[3];
const firstCol = process.argv[4];
const secondCol = process.argv[5];
const thirdCol = process.argv[6];
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const current = fs.readFileSync(file, "utf8");
const updated = current.replace(
new RegExp(
`^\\| \\*\\*${escape(label)}\\*\\* \\| ${escape(firstCol)} \\| ${escape(secondCol)} \\| ${escape(thirdCol)} \\| [0-9]+\\.[0-9]+\\.[0-9]+ \\|$`,
"m"
),
`| **${label}** | ${firstCol} | ${secondCol} | ${thirdCol} | ${version} |`
);
if (updated === current) {
console.error(`Error: could not update README version row in ${file}`);
process.exit(1);
}
fs.writeFileSync(file, updated);
' "$file" "$VERSION" "$label" "$first_col" "$second_col" "$third_col"
}
update_selective_install_repo_version() {
local file="$1"
node -e '
const fs = require("fs");
const file = process.argv[1];
const version = process.argv[2];
const current = fs.readFileSync(file, "utf8");
const updated = current.replace(
/("repoVersion":\s*")[0-9][0-9.]*(")/,
`$1${version}$2`
);
if (updated === current) {
console.error(`Error: could not update repoVersion example in ${file}`);
process.exit(1);
}
fs.writeFileSync(file, updated);
' "$file" "$VERSION"
}
update_agents_version() {
local file="$1"
local label="$2"
node -e '
const fs = require("fs");
const file = process.argv[1];
const version = process.argv[2];
const label = process.argv[3];
const current = fs.readFileSync(file, "utf8");
const updated = current.replace(
new RegExp(`^\\*\\*${label}:\\*\\* [0-9][0-9.]*$`, "m"),
`**${label}:** ${version}`
);
if (updated === current) {
console.error(`Error: could not update AGENTS version line in ${file}`);
process.exit(1);
}
fs.writeFileSync(file, updated);
' "$file" "$VERSION" "$label"
}
update_agent_yaml_version() {
node -e '
const fs = require("fs");
const file = process.argv[1];
const version = process.argv[2];
const current = fs.readFileSync(file, "utf8");
const updated = current.replace(
/^version:\s*[0-9][0-9.]*$/m,
`version: ${version}`
);
if (updated === current) {
console.error(`Error: could not update agent.yaml version line in ${file}`);
process.exit(1);
}
fs.writeFileSync(file, updated);
' "$AGENT_YAML" "$VERSION"
}
update_version_file() {
printf '%s\n' "$VERSION" > "$VERSION_FILE"
}
update_codex_marketplace_version() {
node -e '
const fs = require("fs");
const file = process.argv[1];
const version = process.argv[2];
const marketplace = JSON.parse(fs.readFileSync(file, "utf8"));
if (!marketplace || typeof marketplace !== "object" || !Array.isArray(marketplace.plugins)) {
console.error(`Error: ${file} does not contain a marketplace plugins array`);
process.exit(1);
}
const plugin = marketplace.plugins.find(entry => entry && entry.name === "ecc");
if (!plugin || typeof plugin !== "object") {
console.error(`Error: could not find ecc plugin entry in ${file}`);
process.exit(1);
}
plugin.version = version;
fs.writeFileSync(file, `${JSON.stringify(marketplace, null, 2)}\n`);
' "$CODEX_MARKETPLACE_JSON" "$VERSION"
}
# Update all shipped package/plugin manifests
update_version "$ROOT_PACKAGE_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
update_package_lock_version "$PACKAGE_LOCK_JSON"
update_agents_version "$ROOT_AGENTS_MD" "Version"
update_agents_version "$TR_AGENTS_MD" "Sürüm"
update_agents_version "$ZH_CN_AGENTS_MD" "版本"
update_agent_yaml_version
update_version_file
update_version "$PLUGIN_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
update_version "$MARKETPLACE_JSON" "0,/\"version\": *\"[^\"]*\"/s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
update_codex_marketplace_version
update_version "$CODEX_PLUGIN_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
update_version "$OPENCODE_PACKAGE_JSON" "s|\"version\": *\"[^\"]*\"|\"version\": \"$VERSION\"|"
update_package_lock_version "$OPENCODE_PACKAGE_LOCK_JSON"
update_readme_version_row "$README_FILE" "Version" "Plugin" "Plugin" "Reference config"
update_readme_version_row "$ZH_CN_README_FILE" "版本" "插件" "插件" "参考配置"
update_selective_install_repo_version "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"
# Verify the bumped release surface is still internally consistent before
# writing a release commit, tag, or push.
echo "Verifying OpenCode build and npm pack payload..."
node scripts/build-opencode.js
node tests/scripts/build-opencode.test.js
node tests/plugin-manifest.test.js
# Stage, commit, tag, and push
git add "$ROOT_PACKAGE_JSON" "$PACKAGE_LOCK_JSON" "$ROOT_AGENTS_MD" "$TR_AGENTS_MD" "$ZH_CN_AGENTS_MD" "$AGENT_YAML" "$VERSION_FILE" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$CODEX_MARKETPLACE_JSON" "$CODEX_PLUGIN_JSON" "$OPENCODE_PACKAGE_JSON" "$OPENCODE_PACKAGE_LOCK_JSON" "$README_FILE" "$ZH_CN_README_FILE" "$SELECTIVE_INSTALL_ARCHITECTURE_DOC"
git add "$ROOT_PACKAGE_JSON" "$PLUGIN_JSON" "$MARKETPLACE_JSON" "$OPENCODE_PACKAGE_JSON"
git commit -m "chore: bump plugin version to $VERSION"
git tag "v$VERSION"
git push origin main "v$VERSION"

View File

@@ -309,58 +309,6 @@ function runTests() {
fs.rmSync(repoDir, { recursive: true, force: true });
}) ? passed++ : failed++);
(test('resolves repo-relative paths even when the hook runs from a nested cwd', () => {
const tmpHome = makeTempDir();
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'session-activity-tracker-nested-repo-'));
spawnSync('git', ['init'], { cwd: repoDir, encoding: 'utf8' });
spawnSync('git', ['config', 'user.email', 'ecc@example.com'], { cwd: repoDir, encoding: 'utf8' });
spawnSync('git', ['config', 'user.name', 'ECC Tests'], { cwd: repoDir, encoding: 'utf8' });
const srcDir = path.join(repoDir, 'src');
const nestedCwd = path.join(repoDir, 'subdir');
fs.mkdirSync(srcDir, { recursive: true });
fs.mkdirSync(nestedCwd, { recursive: true });
const trackedFile = path.join(srcDir, 'app.ts');
fs.writeFileSync(trackedFile, 'const count = 1;\n', 'utf8');
spawnSync('git', ['add', 'src/app.ts'], { cwd: repoDir, encoding: 'utf8' });
spawnSync('git', ['commit', '-m', 'init'], { cwd: repoDir, encoding: 'utf8' });
fs.writeFileSync(trackedFile, 'const count = 2;\n', 'utf8');
const input = {
tool_name: 'Write',
tool_input: {
file_path: 'src/app.ts',
content: 'const count = 2;\n',
},
tool_output: { output: 'updated src/app.ts' },
};
const result = runScript(input, {
...withTempHome(tmpHome),
CLAUDE_HOOK_EVENT_NAME: 'PostToolUse',
ECC_SESSION_ID: 'ecc-session-nested-cwd',
}, {
cwd: nestedCwd,
});
assert.strictEqual(result.code, 0);
const metricsFile = path.join(tmpHome, '.claude', 'metrics', 'tool-usage.jsonl');
const row = JSON.parse(fs.readFileSync(metricsFile, 'utf8').trim());
assert.deepStrictEqual(row.file_events, [
{
path: 'src/app.ts',
action: 'modify',
diff_preview: 'const count = 1; -> const count = 2;',
patch_preview: '@@ -1 +1 @@\n-const count = 1;\n+const count = 2;',
},
]);
fs.rmSync(tmpHome, { recursive: true, force: true });
fs.rmSync(repoDir, { recursive: true, force: true });
}) ? passed++ : failed++);
(test('prefers ECC_SESSION_ID over CLAUDE_SESSION_ID and redacts bash summaries', () => {
const tmpHome = makeTempDir();
const input = {

View File

@@ -6,9 +6,6 @@ const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const CURRENT_PACKAGE_VERSION = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8')
).version;
const {
createInstallState,
@@ -69,7 +66,7 @@ function runTests() {
},
],
source: {
repoVersion: CURRENT_PACKAGE_VERSION,
repoVersion: '1.10.0',
repoCommit: 'abc123',
manifestVersion: 1,
},
@@ -103,7 +100,7 @@ function runTests() {
},
operations: [],
source: {
repoVersion: CURRENT_PACKAGE_VERSION,
repoVersion: '1.10.0',
repoCommit: 'abc123',
manifestVersion: 1,
},
@@ -157,7 +154,7 @@ function runTests() {
},
operations: [operation],
source: {
repoVersion: CURRENT_PACKAGE_VERSION,
repoVersion: '1.10.0',
repoCommit: 'abc123',
manifestVersion: 1,
},
@@ -211,7 +208,7 @@ function runTests() {
skippedModules: [],
},
source: {
repoVersion: CURRENT_PACKAGE_VERSION,
repoVersion: '1.10.0',
repoCommit: 'abc123',
manifestVersion: 1,
},

View File

@@ -13,9 +13,6 @@ const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const CURRENT_PACKAGE_VERSION = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf8')
).version;
const { resolveEccRoot, INLINE_RESOLVE } = require('../../scripts/lib/resolve-ecc-root');
@@ -184,7 +181,7 @@ function runTests() {
const homeDir = createTempDir();
try {
const expected = setupLegacyPluginInstall(homeDir, ['marketplace', 'ecc']);
setupPluginCache(homeDir, 'ecc', 'affaan-m', CURRENT_PACKAGE_VERSION);
setupPluginCache(homeDir, 'ecc', 'affaan-m', '1.10.0');
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
@@ -196,7 +193,7 @@ function runTests() {
if (test('discovers plugin root from cache directory', () => {
const homeDir = createTempDir();
try {
const expected = setupPluginCache(homeDir, 'ecc', 'affaan-m', CURRENT_PACKAGE_VERSION);
const expected = setupPluginCache(homeDir, 'ecc', 'affaan-m', '1.10.0');
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
@@ -208,7 +205,7 @@ function runTests() {
const homeDir = createTempDir();
try {
const claudeDir = setupStandardInstall(homeDir);
setupPluginCache(homeDir, 'ecc', 'affaan-m', CURRENT_PACKAGE_VERSION);
setupPluginCache(homeDir, 'ecc', 'affaan-m', '1.10.0');
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, claudeDir,
'Standard install should take precedence over plugin cache');
@@ -221,7 +218,7 @@ function runTests() {
const homeDir = createTempDir();
try {
setupPluginCache(homeDir, 'everything-claude-code', 'legacy-org', '1.7.0');
const expected = setupPluginCache(homeDir, 'ecc', 'affaan-m', CURRENT_PACKAGE_VERSION);
const expected = setupPluginCache(homeDir, 'ecc', 'affaan-m', '1.10.0');
const result = resolveEccRoot({ envRoot: '', homeDir });
// Should find one of them (either is valid)
assert.ok(
@@ -314,7 +311,7 @@ function runTests() {
if (test('INLINE_RESOLVE discovers plugin cache when env var is unset', () => {
const homeDir = createTempDir();
try {
const expected = setupPluginCache(homeDir, 'ecc', 'affaan-m', CURRENT_PACKAGE_VERSION);
const expected = setupPluginCache(homeDir, 'ecc', 'affaan-m', '1.10.0');
const { execFileSync } = require('child_process');
const result = execFileSync('node', [
'-e', `console.log(${INLINE_RESOLVE})`,

View File

@@ -20,20 +20,6 @@ const path = require('path');
const repoRoot = path.resolve(__dirname, '..');
const repoRootWithSep = `${repoRoot}${path.sep}`;
const packageJsonPath = path.join(repoRoot, 'package.json');
const packageLockPath = path.join(repoRoot, 'package-lock.json');
const rootAgentsPath = path.join(repoRoot, 'AGENTS.md');
const trAgentsPath = path.join(repoRoot, 'docs', 'tr', 'AGENTS.md');
const zhCnAgentsPath = path.join(repoRoot, 'docs', 'zh-CN', 'AGENTS.md');
const ptBrReadmePath = path.join(repoRoot, 'docs', 'pt-BR', 'README.md');
const trReadmePath = path.join(repoRoot, 'docs', 'tr', 'README.md');
const rootZhCnReadmePath = path.join(repoRoot, 'README.zh-CN.md');
const agentYamlPath = path.join(repoRoot, 'agent.yaml');
const versionFilePath = path.join(repoRoot, 'VERSION');
const zhCnReadmePath = path.join(repoRoot, 'docs', 'zh-CN', 'README.md');
const selectiveInstallArchitecturePath = path.join(repoRoot, 'docs', 'SELECTIVE-INSTALL-ARCHITECTURE.md');
const opencodePackageJsonPath = path.join(repoRoot, '.opencode', 'package.json');
const opencodePackageLockPath = path.join(repoRoot, '.opencode', 'package-lock.json');
let passed = 0;
let failed = 0;
@@ -78,86 +64,6 @@ function assertSafeRepoRelativePath(relativePath, label) {
);
}
const rootPackage = loadJsonObject(packageJsonPath, 'package.json');
const packageLock = loadJsonObject(packageLockPath, 'package-lock.json');
const opencodePackageLock = loadJsonObject(opencodePackageLockPath, '.opencode/package-lock.json');
const expectedVersion = rootPackage.version;
test('package.json has version field', () => {
assert.ok(expectedVersion, 'Expected package.json version field');
});
test('package-lock.json root version matches package.json', () => {
assert.strictEqual(packageLock.version, expectedVersion);
assert.ok(packageLock.packages && packageLock.packages[''], 'Expected package-lock root package entry');
assert.strictEqual(packageLock.packages[''].version, expectedVersion);
});
test('AGENTS.md version line matches package.json', () => {
const agentsSource = fs.readFileSync(rootAgentsPath, 'utf8');
const match = agentsSource.match(/^\*\*Version:\*\* ([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected AGENTS.md to declare a top-level version line');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/tr/AGENTS.md version line matches package.json', () => {
const agentsSource = fs.readFileSync(trAgentsPath, 'utf8');
const match = agentsSource.match(/^\*\*Sürüm:\*\* ([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected docs/tr/AGENTS.md to declare a top-level version line');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/zh-CN/AGENTS.md version line matches package.json', () => {
const agentsSource = fs.readFileSync(zhCnAgentsPath, 'utf8');
const match = agentsSource.match(/^\*\*版本:\*\* ([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected docs/zh-CN/AGENTS.md to declare a top-level version line');
assert.strictEqual(match[1], expectedVersion);
});
test('agent.yaml version matches package.json', () => {
const agentYamlSource = fs.readFileSync(agentYamlPath, 'utf8');
const match = agentYamlSource.match(/^version:\s*([0-9]+\.[0-9]+\.[0-9]+)$/m);
assert.ok(match, 'Expected agent.yaml to declare a top-level version field');
assert.strictEqual(match[1], expectedVersion);
});
test('VERSION file matches package.json', () => {
const versionFile = fs.readFileSync(versionFilePath, 'utf8').trim();
assert.ok(versionFile, 'Expected VERSION file to be non-empty');
assert.strictEqual(versionFile, expectedVersion);
});
test('docs/SELECTIVE-INSTALL-ARCHITECTURE.md repoVersion example matches package.json', () => {
const source = fs.readFileSync(selectiveInstallArchitecturePath, 'utf8');
const match = source.match(/"repoVersion":\s*"([0-9]+\.[0-9]+\.[0-9]+)"/);
assert.ok(match, 'Expected docs/SELECTIVE-INSTALL-ARCHITECTURE.md to declare a repoVersion example');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/pt-BR/README.md latest release heading matches package.json', () => {
const source = fs.readFileSync(ptBrReadmePath, 'utf8');
assert.ok(
source.includes(`### v${expectedVersion} `),
'Expected docs/pt-BR/README.md to advertise the current release heading',
);
});
test('docs/tr/README.md latest release heading matches package.json', () => {
const source = fs.readFileSync(trReadmePath, 'utf8');
assert.ok(
source.includes(`### v${expectedVersion} `),
'Expected docs/tr/README.md to advertise the current release heading',
);
});
test('README.zh-CN.md latest release heading matches package.json', () => {
const source = fs.readFileSync(rootZhCnReadmePath, 'utf8');
assert.ok(
source.includes(`### v${expectedVersion} `),
'Expected README.zh-CN.md to advertise the current release heading',
);
});
// ── Claude plugin manifest ────────────────────────────────────────────────────
console.log('\n=== .claude-plugin/plugin.json ===\n');
@@ -174,10 +80,6 @@ test('claude plugin.json has version field', () => {
assert.ok(claudePlugin.version, 'Expected version field');
});
test('claude plugin.json version matches package.json', () => {
assert.strictEqual(claudePlugin.version, expectedVersion);
});
test('claude plugin.json uses short plugin slug', () => {
assert.strictEqual(claudePlugin.name, 'ecc');
});
@@ -254,10 +156,6 @@ test('claude marketplace.json has plugins array with a short ecc plugin entry',
assert.strictEqual(claudeMarketplace.plugins[0].name, 'ecc');
});
test('claude marketplace.json plugin version matches package.json', () => {
assert.strictEqual(claudeMarketplace.plugins[0].version, expectedVersion);
});
// ── Codex plugin manifest ─────────────────────────────────────────────────────
// Per official docs: https://platform.openai.com/docs/codex/plugins
// - .codex-plugin/plugin.json is the required manifest
@@ -285,10 +183,6 @@ test('codex plugin.json has version field', () => {
assert.ok(codexPlugin.version, 'Expected version field');
});
test('codex plugin.json version matches package.json', () => {
assert.strictEqual(codexPlugin.version, expectedVersion);
});
test('codex plugin.json skills is a string (not array) per official spec', () => {
assert.strictEqual(
typeof codexPlugin.skills,
@@ -374,7 +268,6 @@ test('marketplace.json exists at .agents/plugins/', () => {
});
const marketplace = loadJsonObject(marketplacePath, '.agents/plugins/marketplace.json');
const opencodePackage = loadJsonObject(opencodePackageJsonPath, '.opencode/package.json');
test('marketplace.json has name field', () => {
assert.ok(marketplace.name, 'Expected name field');
@@ -391,7 +284,6 @@ test('marketplace.json has plugins array with at least one entry', () => {
test('marketplace.json plugin entries have required fields', () => {
for (const plugin of marketplace.plugins) {
assert.ok(plugin.name, `Plugin entry missing name`);
assert.ok(plugin.version, `Plugin "${plugin.name}" missing version`);
assert.ok(plugin.source && plugin.source.source, `Plugin "${plugin.name}" missing source.source`);
assert.ok(plugin.policy && plugin.policy.installation, `Plugin "${plugin.name}" missing policy.installation`);
assert.ok(plugin.category, `Plugin "${plugin.name}" missing category`);
@@ -402,10 +294,6 @@ test('marketplace.json plugin entry uses short plugin slug', () => {
assert.strictEqual(marketplace.plugins[0].name, 'ecc');
});
test('marketplace.json plugin version matches package.json', () => {
assert.strictEqual(marketplace.plugins[0].version, expectedVersion);
});
test('marketplace local plugin path resolves to the repo-root Codex bundle', () => {
for (const plugin of marketplace.plugins) {
if (!plugin.source || plugin.source.source !== 'local') {
@@ -429,30 +317,6 @@ test('marketplace local plugin path resolves to the repo-root Codex bundle', ()
}
});
test('.opencode/package.json version matches package.json', () => {
assert.strictEqual(opencodePackage.version, expectedVersion);
});
test('.opencode/package-lock.json root version matches package.json', () => {
assert.strictEqual(opencodePackageLock.version, expectedVersion);
assert.ok(opencodePackageLock.packages && opencodePackageLock.packages[''], 'Expected .opencode/package-lock root package entry');
assert.strictEqual(opencodePackageLock.packages[''].version, expectedVersion);
});
test('README version row matches package.json', () => {
const readme = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8');
const match = readme.match(/^\| \*\*Version\*\* \| Plugin \| Plugin \| Reference config \| ([0-9][0-9.]*) \|$/m);
assert.ok(match, 'Expected README version summary row');
assert.strictEqual(match[1], expectedVersion);
});
test('docs/zh-CN/README.md version row matches package.json', () => {
const readme = fs.readFileSync(zhCnReadmePath, 'utf8');
const match = readme.match(/^\| \*\*版本\*\* \| 插件 \| 插件 \| 参考配置 \| ([0-9][0-9.]*) \|$/m);
assert.ok(match, 'Expected docs/zh-CN/README.md version summary row');
assert.strictEqual(match[1], expectedVersion);
});
// ── Summary ───────────────────────────────────────────────────────────────────
console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);

View File

@@ -49,9 +49,8 @@ function main() {
const result = spawnSync("npm", ["pack", "--dry-run", "--json"], {
cwd: repoRoot,
encoding: "utf8",
shell: process.platform === "win32",
})
assert.strictEqual(result.status, 0, result.error?.message || result.stderr)
assert.strictEqual(result.status, 0, result.stderr)
const packOutput = JSON.parse(result.stdout)
const packagedPaths = new Set(packOutput[0]?.files?.map((file) => file.path) ?? [])
@@ -68,42 +67,6 @@ function main() {
packagedPaths.has(".opencode/dist/tools/index.js"),
"npm pack should include compiled OpenCode tool output"
)
assert.ok(
packagedPaths.has(".claude-plugin/marketplace.json"),
"npm pack should include .claude-plugin/marketplace.json"
)
assert.ok(
packagedPaths.has(".claude-plugin/plugin.json"),
"npm pack should include .claude-plugin/plugin.json"
)
assert.ok(
packagedPaths.has(".codex-plugin/plugin.json"),
"npm pack should include .codex-plugin/plugin.json"
)
assert.ok(
packagedPaths.has(".agents/plugins/marketplace.json"),
"npm pack should include .agents/plugins/marketplace.json"
)
assert.ok(
packagedPaths.has(".opencode/package.json"),
"npm pack should include .opencode/package.json"
)
assert.ok(
packagedPaths.has(".opencode/package-lock.json"),
"npm pack should include .opencode/package-lock.json"
)
assert.ok(
packagedPaths.has("agent.yaml"),
"npm pack should include agent.yaml"
)
assert.ok(
packagedPaths.has("AGENTS.md"),
"npm pack should include AGENTS.md"
)
assert.ok(
packagedPaths.has("VERSION"),
"npm pack should include VERSION"
)
}],
]

View File

@@ -1,71 +0,0 @@
/**
* Source-level tests for scripts/release.sh
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const scriptPath = path.join(__dirname, '..', '..', 'scripts', 'release.sh');
const source = fs.readFileSync(scriptPath, 'utf8');
function test(name, fn) {
try {
fn();
console.log(`${name}`);
return true;
} catch (error) {
console.log(`${name}`);
console.log(` Error: ${error.message}`);
return false;
}
}
function runTests() {
console.log('\n=== Testing release.sh ===\n');
let passed = 0;
let failed = 0;
if (test('release script rejects untracked files when checking cleanliness', () => {
assert.ok(
source.includes('git status --porcelain --untracked-files=all'),
'release.sh should use git status --porcelain --untracked-files=all for cleanliness checks'
);
})) passed++; else failed++;
if (test('release script reruns release metadata sync validation before commit/tag', () => {
const syncCheckIndex = source.lastIndexOf('node tests/plugin-manifest.test.js');
const commitIndex = source.indexOf('git commit -m "chore: bump plugin version to $VERSION"');
assert.ok(syncCheckIndex >= 0, 'release.sh should run plugin-manifest.test.js');
assert.ok(commitIndex >= 0, 'release.sh should create the release commit');
assert.ok(
syncCheckIndex < commitIndex,
'plugin-manifest.test.js should run before the release commit is created'
);
})) passed++; else failed++;
if (test('release script verifies npm pack payload after version updates and before commit/tag', () => {
const updateIndex = source.indexOf('update_version "$ROOT_PACKAGE_JSON"');
const packCheckIndex = source.indexOf('node tests/scripts/build-opencode.test.js');
const commitIndex = source.indexOf('git commit -m "chore: bump plugin version to $VERSION"');
assert.ok(updateIndex >= 0, 'release.sh should update package version fields');
assert.ok(packCheckIndex >= 0, 'release.sh should run build-opencode.test.js');
assert.ok(commitIndex >= 0, 'release.sh should create the release commit');
assert.ok(
updateIndex < packCheckIndex,
'build-opencode.test.js should run after versioned files are updated'
);
assert.ok(
packCheckIndex < commitIndex,
'build-opencode.test.js should run before the release commit is created'
);
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
runTests();

View File

@@ -169,30 +169,30 @@ __metadata:
languageName: node
linkType: hard
"@opencode-ai/plugin@npm:^1.0.0":
version: 1.3.15
resolution: "@opencode-ai/plugin@npm:1.3.15"
"@opencode-ai/plugin@npm:^1.4.3":
version: 1.4.3
resolution: "@opencode-ai/plugin@npm:1.4.3"
dependencies:
"@opencode-ai/sdk": "npm:1.3.15"
"@opencode-ai/sdk": "npm:1.4.3"
zod: "npm:4.1.8"
peerDependencies:
"@opentui/core": ">=0.1.96"
"@opentui/solid": ">=0.1.96"
"@opentui/core": ">=0.1.97"
"@opentui/solid": ">=0.1.97"
peerDependenciesMeta:
"@opentui/core":
optional: true
"@opentui/solid":
optional: true
checksum: 10c0/1a662ff700812223310612f3c8c7fd4465eda5763d726ec4d29d0eae26babf344ef176c9b987d79fe1e29c8a498178881a47d7080bb9f4db3e70dad59eb8cd9e
checksum: 10c0/a20328a691a674638e4718c1fb911ea68b60fc7560f1bf314324114ccdcabbddc12e98c4fc9f3aad69e92aaaac7edbd44216bf036955ec5d1f50282430ab06ae
languageName: node
linkType: hard
"@opencode-ai/sdk@npm:1.3.15":
version: 1.3.15
resolution: "@opencode-ai/sdk@npm:1.3.15"
"@opencode-ai/sdk@npm:1.4.3":
version: 1.4.3
resolution: "@opencode-ai/sdk@npm:1.4.3"
dependencies:
cross-spawn: "npm:7.0.6"
checksum: 10c0/3957ae62e0ec1e339d9493e03a2440c95afdd64a608a2dc9db8383338650318a294280b2142305db5b0147badacbefa0d07e949d31167e5a4a49c9d057d016fa
checksum: 10c0/edba27ef01ecfb6fde7df2348f953aab64f2e7b99e9cd5b155474e7e02cc0db62da242d9edcd5b704110b9ef82bc16633d99d25eaa812d4279badede71ae419f
languageName: node
linkType: hard
@@ -548,7 +548,7 @@ __metadata:
dependencies:
"@eslint/js": "npm:^9.39.2"
"@iarna/toml": "npm:^2.2.5"
"@opencode-ai/plugin": "npm:^1.0.0"
"@opencode-ai/plugin": "npm:^1.4.3"
"@types/node": "npm:^20.19.24"
ajv: "npm:^8.18.0"
c8: "npm:^11.0.0"