diff --git a/AGENTS.md b/AGENTS.md index 245eedf3d4b..1197f6fb48f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,6 +72,8 @@ - `docs/zh-CN/**` is generated; do not edit unless the user explicitly asks. - Pipeline: update English docs → adjust glossary (`docs/.i18n/glossary.zh-CN.json`) → run `scripts/docs-i18n` → apply targeted fixes only if instructed. +- Before rerunning `scripts/docs-i18n`, add glossary entries for any new technical terms, page titles, or short nav labels that must stay in English or use a fixed translation (for example `Doctor` or `Polls`). +- `pnpm docs:check-i18n-glossary` enforces glossary coverage for changed English doc titles and short internal doc labels before translation reruns. - Translation memory: `docs/.i18n/zh-CN.tm.jsonl` (generated). - See `docs/.i18n/README.md`. - The pipeline can be slow/inefficient; if it’s dragging, ping @jospalmbier on Discord instead of hacking around it. @@ -97,7 +99,7 @@ - Prefer Bun for TypeScript execution (scripts, dev, tests): `bun ` / `bunx `. - Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`. - Node remains supported for running built output (`dist/*`) and production installs. -- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`. +- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. - Type-check/build: `pnpm build` - TypeScript checks: `pnpm tsgo` - Lint/format: `pnpm check` @@ -179,7 +181,7 @@ - Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable. - Environment variables: see `~/.profile`. - Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples. -- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them. +- Release flow: use the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md) for the actual runbook; use `docs/reference/RELEASING.md` for the public release policy. ## GHSA (Repo Advisory) Patch/Publish @@ -256,14 +258,13 @@ - If shared guardrails are available locally, review them; otherwise follow this repo's guidance. - SwiftUI state management (iOS/macOS): prefer the `Observation` framework (`@Observable`, `@Bindable`) over `ObservableObject`/`@StateObject`; don’t introduce new `ObservableObject` unless required for compatibility, and migrate existing usages when touching related code. - Connection providers: when adding a new connection, update every UI surface and docs (macOS app, web UI, mobile if applicable, onboarding/overview docs) and add matching status + configuration forms so provider lists and settings stay in sync. -- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), `docs/platforms/mac/release.md` (APP_VERSION/APP_BUILD examples), Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION). +- Version locations: `package.json` (CLI), `apps/android/app/build.gradle.kts` (versionName/versionCode), `apps/ios/Sources/Info.plist` + `apps/ios/Tests/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `apps/macos/Sources/OpenClaw/Resources/Info.plist` (CFBundleShortVersionString/CFBundleVersion), `docs/install/updating.md` (pinned npm version), and Peekaboo Xcode projects/Info.plists (MARKETING_VERSION/CURRENT_PROJECT_VERSION). - "Bump version everywhere" means all version locations above **except** `appcast.xml` (only touch appcast when cutting a new macOS Sparkle release). - **Restart apps:** “restart iOS/Android apps” means rebuild (recompile/install) and relaunch, not just kill/launch. - **Device checks:** before testing, verify connected real devices (iOS/Android) before reaching for simulators/emulators. - iOS Team ID lookup: `security find-identity -p codesigning -v` → use Apple Development (…) TEAMID. Fallback: `defaults read com.apple.dt.Xcode IDEProvisioningTeamIdentifiers`. - A2UI bundle hash: `src/canvas-host/a2ui/.bundle.hash` is auto-generated; ignore unexpected changes, and only regenerate via `pnpm canvas:a2ui:bundle` (or `scripts/bundle-a2ui.sh`) when needed. Commit the hash as a separate commit. -- Release signing/notary keys are managed outside the repo; follow internal release docs. -- Notary auth env vars (`APP_STORE_CONNECT_ISSUER_ID`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_API_KEY_P8`) are expected in your environment (per internal release docs). +- Release signing/notary credentials are managed outside the repo; maintainers keep that setup in the private [maintainer release docs](https://github.com/openclaw/maintainers/tree/main/release). - **Multi-agent safety:** do **not** create/apply/drop `git stash` entries unless explicitly requested (this includes `git pull --rebase --autostash`). Assume other agents may be working; keep unrelated WIP untouched and avoid cross-cutting state changes. - **Multi-agent safety:** when the user says "push", you may `git pull --rebase` to integrate latest changes (never discard other agents' work). When the user says "commit", scope to your changes only. When the user says "commit all", commit everything in grouped chunks. - **Multi-agent safety:** do **not** create/remove/modify `git worktree` checkouts (or edit `.worktrees/*`) unless explicitly requested. @@ -290,35 +291,12 @@ - Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step. - Beta release guardrail: when using a beta Git tag (for example `vYYYY.M.D-beta.N`), publish npm with a matching beta version suffix (for example `YYYY.M.D-beta.N`) rather than a plain version on `--tag beta`; otherwise the plain version name gets consumed/blocked. -## NPM + 1Password (publish/verify) +## Release Auth -- Use the 1password skill; all `op` commands must run inside a fresh tmux session. -- Correct 1Password path for npm release auth: `op://Private/Npmjs` (use that item; OTP stays `op://Private/Npmjs/one-time password?attribute=otp`). -- Sign in: `eval "$(op signin --account my.1password.com)"` (app unlocked + integration on). -- OTP: `op read 'op://Private/Npmjs/one-time password?attribute=otp'`. -- Publish: `npm publish --access public --otp=""` (run from the package dir). -- Verify without local npmrc side effects: `npm view version --userconfig "$(mktemp)"`. -- Kill the tmux session after publish. - -## Plugin Release Fast Path (no core `openclaw` publish) - -- Release only already-on-npm plugins. Source list is in `docs/reference/RELEASING.md` under "Current npm plugin list". -- Run all CLI `op` calls and `npm publish` inside tmux to avoid hangs/interruption: - - `tmux new -d -s release-plugins-$(date +%Y%m%d-%H%M%S)` - - `eval "$(op signin --account my.1password.com)"` -- 1Password helpers: - - password used by `npm login`: - `op item get Npmjs --format=json | jq -r '.fields[] | select(.id=="password").value'` - - OTP: - `op read 'op://Private/Npmjs/one-time password?attribute=otp'` -- Fast publish loop (local helper script in `/tmp` is fine; keep repo clean): - - compare local plugin `version` to `npm view version` - - only run `npm publish --access public --otp=""` when versions differ - - skip if package is missing on npm or version already matches. -- Keep `openclaw` untouched: never run publish from repo root unless explicitly requested. -- Post-check for each release: - - per-plugin: `npm view @openclaw/ version --userconfig "$(mktemp)"` should be `2026.2.17` - - core guard: `npm view openclaw version --userconfig "$(mktemp)"` should stay at previous version unless explicitly requested. +- Core `openclaw` publish uses GitHub trusted publishing; do not use `NPM_TOKEN` or the plugin OTP flow for core releases. +- Separate `@openclaw/*` plugin publishes use a different maintainer-only auth flow. +- Plugin scope: only publish already-on-npm `@openclaw/*` plugins. Bundled disk-tree-only plugins stay out. +- Maintainers: private 1Password item names, tmux rules, plugin publish helpers, and local mac signing/notary setup live in the private [maintainer release docs](https://github.com/openclaw/maintainers/blob/main/release/README.md). ## Changelog Release Notes diff --git a/docs/.i18n/glossary.zh-CN.json b/docs/.i18n/glossary.zh-CN.json index bde108074c2..f8941862b94 100644 --- a/docs/.i18n/glossary.zh-CN.json +++ b/docs/.i18n/glossary.zh-CN.json @@ -123,6 +123,22 @@ "source": "Network model", "target": "网络模型" }, + { + "source": "Doctor", + "target": "Doctor" + }, + { + "source": "Polls", + "target": "投票" + }, + { + "source": "Release Policy", + "target": "发布策略" + }, + { + "source": "Release policy", + "target": "发布策略" + }, { "source": "for full details", "target": "了解详情" diff --git a/docs/docs.json b/docs/docs.json index 98c88e0177c..8855a7335d6 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -469,7 +469,7 @@ }, { "source": "/mac/release", - "destination": "/platforms/mac/release" + "destination": "/reference/RELEASING" }, { "source": "/mac/remote", @@ -1166,7 +1166,6 @@ "platforms/mac/permissions", "platforms/mac/remote", "platforms/mac/signing", - "platforms/mac/release", "platforms/mac/bundled-gateway", "platforms/mac/xpc", "platforms/mac/skills", @@ -1351,7 +1350,7 @@ "pages": ["reference/credits"] }, { - "group": "Release notes", + "group": "Release policy", "pages": ["reference/RELEASING", "reference/test"] }, { @@ -1750,7 +1749,6 @@ "zh-CN/platforms/mac/permissions", "zh-CN/platforms/mac/remote", "zh-CN/platforms/mac/signing", - "zh-CN/platforms/mac/release", "zh-CN/platforms/mac/bundled-gateway", "zh-CN/platforms/mac/xpc", "zh-CN/platforms/mac/skills", @@ -1933,7 +1931,7 @@ "pages": ["zh-CN/reference/credits"] }, { - "group": "发布说明", + "group": "发布策略", "pages": ["zh-CN/reference/RELEASING", "zh-CN/reference/test"] }, { diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md deleted file mode 100644 index 5276d46848e..00000000000 --- a/docs/platforms/mac/release.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -summary: "OpenClaw macOS release checklist (Sparkle feed, packaging, signing)" -read_when: - - Cutting or validating a OpenClaw macOS release - - Updating the Sparkle appcast or feed assets -title: "macOS Release" ---- - -# OpenClaw macOS release (Sparkle) - -This app now ships Sparkle auto-updates. Release builds must be Developer ID–signed, zipped, and published with a signed appcast entry. - -## Prereqs - -- Developer ID Application cert installed (example: `Developer ID Application: ()`). -- Sparkle private key path set in the environment as `SPARKLE_PRIVATE_KEY_FILE` (path to your Sparkle ed25519 private key; public key baked into Info.plist). If it is missing, check `~/.profile`. -- Notary credentials (keychain profile or API key) for `xcrun notarytool` if you want Gatekeeper-safe DMG/zip distribution. - - We use a Keychain profile named `openclaw-notary`, created from App Store Connect API key env vars in your shell profile: - - `APP_STORE_CONNECT_API_KEY_P8`, `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID` - - `echo "$APP_STORE_CONNECT_API_KEY_P8" | sed 's/\\n/\n/g' > /tmp/openclaw-notary.p8` - - `xcrun notarytool store-credentials "openclaw-notary" --key /tmp/openclaw-notary.p8 --key-id "$APP_STORE_CONNECT_KEY_ID" --issuer "$APP_STORE_CONNECT_ISSUER_ID"` -- `pnpm` deps installed (`pnpm install --config.node-linker=hoisted`). -- Sparkle tools are fetched automatically via SwiftPM at `apps/macos/.build/artifacts/sparkle/Sparkle/bin/` (`sign_update`, `generate_appcast`, etc.). - -## Build & package - -Notes: - -- `APP_BUILD` maps to `CFBundleVersion`/`sparkle:version`; keep it numeric + monotonic (no `-beta`), or Sparkle compares it as equal. -- If `APP_BUILD` is omitted, `scripts/package-mac-app.sh` derives a Sparkle-safe default from `APP_VERSION` (`YYYYMMDDNN`: stable defaults to `90`, prereleases use a suffix-derived lane) and uses the higher of that value and git commit count. -- You can still override `APP_BUILD` explicitly when release engineering needs a specific monotonic value. -- For `BUILD_CONFIG=release`, `scripts/package-mac-app.sh` now defaults to universal (`arm64 x86_64`) automatically. You can still override with `BUILD_ARCHS=arm64` or `BUILD_ARCHS=x86_64`. For local/dev builds (`BUILD_CONFIG=debug`), it defaults to the current architecture (`$(uname -m)`). -- Use `scripts/package-mac-dist.sh` for release artifacts (zip + DMG + notarization). Use `scripts/package-mac-app.sh` for local/dev packaging. - -```bash -# From repo root; set release IDs so Sparkle feed is enabled. -# This command builds release artifacts without notarization. -# APP_BUILD must be numeric + monotonic for Sparkle compare. -# Default is auto-derived from APP_VERSION when omitted. -SKIP_NOTARIZE=1 \ -BUNDLE_ID=ai.openclaw.mac \ -APP_VERSION=2026.3.13 \ -BUILD_CONFIG=release \ -SIGN_IDENTITY="Developer ID Application: ()" \ -scripts/package-mac-dist.sh - -# `package-mac-dist.sh` already creates the zip + DMG. -# If you used `package-mac-app.sh` directly instead, create them manually: -# If you want notarization/stapling in this step, use the NOTARIZE command below. -ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.3.13.zip - -# Optional: build a styled DMG for humans (drag to /Applications) -scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.3.13.dmg - -# Recommended: build + notarize/staple zip + DMG -# First, create a keychain profile once: -# xcrun notarytool store-credentials "openclaw-notary" \ -# --apple-id "" --team-id "" --password "" -NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \ -BUNDLE_ID=ai.openclaw.mac \ -APP_VERSION=2026.3.13 \ -BUILD_CONFIG=release \ -SIGN_IDENTITY="Developer ID Application: ()" \ -scripts/package-mac-dist.sh - -# Optional: ship dSYM alongside the release -ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.3.13.dSYM.zip -``` - -## Appcast entry - -Use the release note generator so Sparkle renders formatted HTML notes: - -```bash -SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.3.13.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml -``` - -Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry. -Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing. - -## Publish & verify - -- Upload `OpenClaw-2026.3.13.zip` (and `OpenClaw-2026.3.13.dSYM.zip`) to the GitHub release for tag `v2026.3.13`. -- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`. -- Sanity checks: - - `curl -I https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml` returns 200. - - `curl -I ` returns 200 after assets upload. - - On a previous public build, run “Check for Updates…” from the About tab and verify Sparkle installs the new build cleanly. - -Definition of done: signed app + appcast are published, update flow works from an older installed version, and release assets are attached to the GitHub release. diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index d94f3866c83..275675c7dba 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -1,161 +1,42 @@ --- -title: "Release Checklist" -summary: "Step-by-step release checklist for npm + macOS app" +title: "Release Policy" +summary: "Public release channels, version naming, and cadence" read_when: - - Cutting a new npm release - - Cutting a new macOS app release - - Verifying metadata before publishing + - Looking for public release channel definitions + - Looking for version naming and cadence --- -# Release Checklist (npm + macOS) +# Release Policy -Use `pnpm` from the repo root with Node 24 by default. Node 22 LTS, currently `22.16+`, remains supported for compatibility. Keep the working tree clean before tagging/publishing. +OpenClaw has three public release lanes: -## Operator trigger +- stable: tagged releases that publish to npm `latest` +- beta: prerelease tags that publish to npm `beta` +- dev: the moving head of `main` -When the operator says “release”, immediately do this preflight (no extra questions unless blocked): - -- Read this doc and `docs/platforms/mac/release.md`. -- Load env from `~/.profile` and confirm `SPARKLE_PRIVATE_KEY_FILE` + App Store Connect vars are set (SPARKLE_PRIVATE_KEY_FILE should live in `~/.profile`). -- Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed. - -## Versioning - -Current OpenClaw releases use date-based versioning. +## Version naming - Stable release version: `YYYY.M.D` - Git tag: `vYYYY.M.D` - - Examples from repo history: `v2026.2.26`, `v2026.3.8` - Beta prerelease version: `YYYY.M.D-beta.N` - Git tag: `vYYYY.M.D-beta.N` - - Examples from repo history: `v2026.2.15-beta.1`, `v2026.3.8-beta.1` -- Fallback correction tag: `vYYYY.M.D-N` - - Use only as a last-resort recovery tag when a published immutable release burned the original stable tag and you cannot reuse it. - - The npm package version stays `YYYY.M.D`; the `-N` suffix is only for the git tag and GitHub release. - - Prefer betas for normal pre-release iteration, then cut a clean stable tag once ready. -- Use the same version string everywhere, minus the leading `v` where Git tags are not used: - - `package.json`: `2026.3.8` - - Git tag: `v2026.3.8` - - GitHub release title: `openclaw 2026.3.8` -- Do not zero-pad month or day. Use `2026.3.8`, not `2026.03.08`. -- Stable and beta are npm dist-tags, not separate release lines: - - `latest` = stable - - `beta` = prerelease/testing -- Dev is the moving head of `main`, not a normal git-tagged release. -- The tag-triggered preview run accepts stable, beta, and fallback correction tags, and rejects versions whose CalVer date is more than 2 UTC calendar days away from the release date. +- Do not zero-pad month or day +- `latest` means the current stable npm release +- `beta` means the current prerelease npm release +- Beta releases may ship before the macOS app catches up -Historical note: +## Release cadence -- Older tags such as `v2026.1.11-1`, `v2026.2.6-3`, and `v2.0.0-beta2` exist in repo history. -- Treat correction tags as a fallback-only escape hatch. New releases should still use `vYYYY.M.D` for stable and `vYYYY.M.D-beta.N` for beta. +- Releases move beta-first +- Stable follows only after the latest beta is validated +- Detailed release procedure, approvals, credentials, and recovery notes are + maintainer-only -1. **Version & metadata** +## Public references -- [ ] Bump `package.json` version (e.g., `2026.1.29`). -- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs. -- [ ] Update CLI/version strings in [`src/version.ts`](https://github.com/openclaw/openclaw/blob/main/src/version.ts) and the Baileys user agent in [`src/web/session.ts`](https://github.com/openclaw/openclaw/blob/main/src/web/session.ts). -- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) for `openclaw`. -- [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current. +- [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml) +- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts) -2. **Build & artifacts** - -- [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/openclaw/openclaw/blob/main/src/canvas-host/a2ui/a2ui.bundle.js). -- [ ] `pnpm run build` (regenerates `dist/`). -- [ ] Verify npm package `files` includes all required `dist/*` folders (notably `dist/node-host/**` and `dist/acp/**` for headless node + ACP CLI). -- [ ] Confirm `dist/build-info.json` exists and includes the expected `commit` hash (CLI banner uses this for npm installs). -- [ ] Optional: `npm pack --pack-destination /tmp` after the build; inspect the tarball contents and keep it handy for the GitHub release (do **not** commit it). - -3. **Changelog & docs** - -- [ ] Update `CHANGELOG.md` with user-facing highlights (create the file if missing); keep entries strictly descending by version. -- [ ] Ensure README examples/flags match current CLI behavior (notably new commands or options). - -4. **Validation** - -- [ ] `pnpm build` -- [ ] `pnpm check` -- [ ] `pnpm test` (or `pnpm test:coverage` if you need coverage output) -- [ ] `pnpm release:check` (verifies npm pack contents) -- [ ] If `pnpm config:docs:check` fails as part of release validation and the config-surface change is intentional, run `pnpm config:docs:gen`, review `docs/.generated/config-baseline.json` and `docs/.generated/config-baseline.jsonl`, commit the updated baselines, then rerun `pnpm release:check`. -- [ ] `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` (Docker install smoke test, fast path; required before release) - - If the immediate previous npm release is known broken, set `OPENCLAW_INSTALL_SMOKE_PREVIOUS=` or `OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS=1` for the preinstall step. -- [ ] (Optional) Full installer smoke (adds non-root + CLI coverage): `pnpm test:install:smoke` -- [ ] (Optional) Installer E2E (Docker, runs `curl -fsSL https://openclaw.ai/install.sh | bash`, onboards, then runs real tool calls): - - `pnpm test:install:e2e:openai` (requires `OPENAI_API_KEY`) - - `pnpm test:install:e2e:anthropic` (requires `ANTHROPIC_API_KEY`) - - `pnpm test:install:e2e` (requires both keys; runs both providers) -- [ ] (Optional) Spot-check the web gateway if your changes affect send/receive paths. - -5. **macOS app (Sparkle)** - -- [ ] Build + sign the macOS app, then zip it for distribution. -- [ ] Generate the Sparkle appcast (HTML notes via [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)) and update `appcast.xml`. -- [ ] Keep the app zip (and optional dSYM zip) ready to attach to the GitHub release. -- [ ] Follow [macOS release](/platforms/mac/release) for the exact commands and required env vars. - - `APP_BUILD` must be numeric + monotonic (no `-beta`) so Sparkle compares versions correctly. - - If notarizing, use the `openclaw-notary` keychain profile created from App Store Connect API env vars (see [macOS release](/platforms/mac/release)). - -6. **Publish (npm)** - -- [ ] Confirm git status is clean; commit and push as needed. -- [ ] Confirm npm trusted publishing is configured for the `openclaw` package. -- [ ] Do not rely on an `NPM_TOKEN` secret for this workflow; the publish job uses GitHub OIDC trusted publishing. -- [ ] Push the matching git tag to trigger the preview run in `.github/workflows/openclaw-npm-release.yml`. -- [ ] Run `OpenClaw NPM Release` manually with the same tag to publish after `npm-release` environment approval. - - Stable tags publish to npm `latest`. - - Beta tags publish to npm `beta`. - - Fallback correction tags like `v2026.3.13-1` map to npm version `2026.3.13`. - - Both the preview run and the manual publish run reject tags that do not map back to `package.json`, are not on `main`, or whose CalVer date is more than 2 UTC calendar days away from the release date. - - If `openclaw@YYYY.M.D` is already published, a fallback correction tag is still useful for GitHub release and Docker recovery, but npm publish will not republish that version. -- [ ] Verify the registry: `npm view openclaw version`, `npm view openclaw dist-tags`, and `npx -y openclaw@X.Y.Z --version` (or `--help`). - -### Troubleshooting (notes from 2.0.0-beta2 release) - -- **npm pack/publish hangs or produces huge tarball**: the macOS app bundle in `dist/OpenClaw.app` (and release zips) get swept into the package. Fix by whitelisting publish contents via `package.json` `files` (include dist subdirs, docs, skills; exclude app bundles). Confirm with `npm pack --dry-run` that `dist/OpenClaw.app` is not listed. -- **npm auth web loop for dist-tags**: use legacy auth to get an OTP prompt: - - `NPM_CONFIG_AUTH_TYPE=legacy npm dist-tag add openclaw@X.Y.Z latest` -- **`npx` verification fails with `ECOMPROMISED: Lock compromised`**: retry with a fresh cache: - - `NPM_CONFIG_CACHE=/tmp/npm-cache-$(date +%s) npx -y openclaw@X.Y.Z --version` -- **Tag needs recovery after a late fix**: if the original stable tag is tied to an immutable GitHub release, mint a fallback correction tag like `vX.Y.Z-1` instead of trying to force-update `vX.Y.Z`. - - Keep the npm package version at `X.Y.Z`; the correction suffix is for the git tag and GitHub release only. - - Use this only as a last resort. For normal iteration, prefer beta tags and then cut a clean stable release. - -7. **GitHub release + appcast** - -- [ ] Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z` (or `git push --tags`). - - Pushing the tag also triggers the npm release workflow. -- [ ] Create/refresh the GitHub release for `vX.Y.Z` with **title `openclaw X.Y.Z`** (not just the tag); body should include the **full** changelog section for that version (Highlights + Changes + Fixes), inline (no bare links), and **must not repeat the title inside the body**. -- [ ] Attach artifacts: `npm pack` tarball (optional), `OpenClaw-X.Y.Z.zip`, and `OpenClaw-X.Y.Z.dSYM.zip` (if generated). -- [ ] Commit the updated `appcast.xml` and push it (Sparkle feeds from main). -- [ ] From a clean temp directory (no `package.json`), run `npx -y openclaw@X.Y.Z send --help` to confirm install/CLI entrypoints work. -- [ ] Announce/share release notes. - -## Plugin publish scope (npm) - -We only publish **existing npm plugins** under the `@openclaw/*` scope. Bundled -plugins that are not on npm stay **disk-tree only** (still shipped in -`extensions/**`). - -Process to derive the list: - -1. `npm search @openclaw --json` and capture the package names. -2. Compare with `extensions/*/package.json` names. -3. Publish only the **intersection** (already on npm). - -Current npm plugin list (update as needed): - -- @openclaw/bluebubbles -- @openclaw/diagnostics-otel -- @openclaw/discord -- @openclaw/feishu -- @openclaw/lobster -- @openclaw/matrix -- @openclaw/msteams -- @openclaw/nextcloud-talk -- @openclaw/nostr -- @openclaw/voice-call -- @openclaw/zalo -- @openclaw/zalouser - -Release notes must also call out **new optional bundled plugins** that are **not -on by default** (example: `tlon`). +Maintainers use the private release docs in +[`openclaw/maintainers/release/README.md`](https://github.com/openclaw/maintainers/blob/main/release/README.md) +for the actual runbook. diff --git a/docs/start/hubs.md b/docs/start/hubs.md index cad1e41e114..9833b467378 100644 --- a/docs/start/hubs.md +++ b/docs/start/hubs.md @@ -157,7 +157,6 @@ Use these hubs to discover every page, including deep dives and reference docs t - [macOS permissions](/platforms/mac/permissions) - [macOS remote](/platforms/mac/remote) - [macOS signing](/platforms/mac/signing) -- [macOS release](/platforms/mac/release) - [macOS gateway (launchd)](/platforms/mac/bundled-gateway) - [macOS XPC](/platforms/mac/xpc) - [macOS skills](/platforms/mac/skills) @@ -190,5 +189,5 @@ Use these hubs to discover every page, including deep dives and reference docs t ## Testing + release - [Testing](/reference/test) -- [Release checklist](/reference/RELEASING) +- [Release policy](/reference/RELEASING) - [Device models](/reference/device-models) diff --git a/docs/zh-CN/AGENTS.md b/docs/zh-CN/AGENTS.md index cbf46cc310f..719a3576480 100644 --- a/docs/zh-CN/AGENTS.md +++ b/docs/zh-CN/AGENTS.md @@ -12,7 +12,7 @@ - 目标文档:`docs/zh-CN/**/*.md` - 术语表:`docs/.i18n/glossary.zh-CN.json` - 翻译记忆库:`docs/.i18n/zh-CN.tm.jsonl` -- 提示词规则:`scripts/docs-i18n/translator.go` +- 提示词规则:`scripts/docs-i18n/prompt.go` 常用运行方式: @@ -31,6 +31,8 @@ go run scripts/docs-i18n/main.go -mode segment docs/channels/matrix.md 注意事项: - doc 模式用于整页翻译;segment 模式用于小范围修补(依赖 TM)。 +- 新增技术术语、页面标题或短导航标签时,先更新 `docs/.i18n/glossary.zh-CN.json`,再跑 `doc` 模式;不要指望模型自行保留英文术语或固定译名。 +- `pnpm docs:check-i18n-glossary` 会检查变更过的英文文档标题和短内部链接标签是否已写入 glossary。 - 超大文件若超时,优先做**定点替换**或拆分后再跑。 - 翻译后检查中文引号、CJK-Latin 间距和术语一致性。 diff --git a/docs/zh-CN/platforms/mac/release.md b/docs/zh-CN/platforms/mac/release.md deleted file mode 100644 index d087a2bcb8c..00000000000 --- a/docs/zh-CN/platforms/mac/release.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -read_when: - - 制作或验证 OpenClaw macOS 发布版本 - - 更新 Sparkle appcast 或订阅源资源 -summary: OpenClaw macOS 发布清单(Sparkle 订阅源、打包、签名) -title: macOS 发布 -x-i18n: - generated_at: "2026-02-01T21:33:17Z" - model: claude-opus-4-5 - provider: pi - source_hash: 703c08c13793cd8c96bd4c31fb4904cdf4ffff35576e7ea48a362560d371cb30 - source_path: platforms/mac/release.md - workflow: 15 ---- - -# OpenClaw macOS 发布(Sparkle) - -本应用现已支持 Sparkle 自动更新。发布构建必须经过 Developer ID 签名、压缩,并发布包含签名的 appcast 条目。 - -## 前提条件 - -- 已安装 Developer ID Application 证书(示例:`Developer ID Application: ()`)。 -- 环境变量 `SPARKLE_PRIVATE_KEY_FILE` 已设置为 Sparkle ed25519 私钥路径(公钥已嵌入 Info.plist)。如果缺失,请检查 `~/.profile`。 -- 用于 `xcrun notarytool` 的公证凭据(钥匙串配置文件或 API 密钥),以实现通过 Gatekeeper 安全分发的 DMG/zip。 - - 我们使用名为 `openclaw-notary` 的钥匙串配置文件,由 shell 配置文件中的 App Store Connect API 密钥环境变量创建: - - `APP_STORE_CONNECT_API_KEY_P8`、`APP_STORE_CONNECT_KEY_ID`、`APP_STORE_CONNECT_ISSUER_ID` - - `echo "$APP_STORE_CONNECT_API_KEY_P8" | sed 's/\\n/\n/g' > /tmp/openclaw-notary.p8` - - `xcrun notarytool store-credentials "openclaw-notary" --key /tmp/openclaw-notary.p8 --key-id "$APP_STORE_CONNECT_KEY_ID" --issuer "$APP_STORE_CONNECT_ISSUER_ID"` -- 已安装 `pnpm` 依赖(`pnpm install --config.node-linker=hoisted`)。 -- Sparkle 工具通过 SwiftPM 自动获取,位于 `apps/macos/.build/artifacts/sparkle/Sparkle/bin/`(`sign_update`、`generate_appcast` 等)。 - -## 构建与打包 - -注意事项: - -- `APP_BUILD` 映射到 `CFBundleVersion`/`sparkle:version`;保持纯数字且单调递增(不含 `-beta`),否则 Sparkle 会将其视为相同版本。 -- 默认为当前架构(`$(uname -m)`)。对于发布/通用构建,设置 `BUILD_ARCHS="arm64 x86_64"`(或 `BUILD_ARCHS=all`)。 -- 使用 `scripts/package-mac-dist.sh` 生成发布产物(zip + DMG + 公证)。使用 `scripts/package-mac-app.sh` 进行本地/开发打包。 - -```bash -# 从仓库根目录运行;设置发布 ID 以启用 Sparkle 订阅源。 -# APP_BUILD 必须为纯数字且单调递增,以便 Sparkle 正确比较。 -BUNDLE_ID=bot.molt.mac \ -APP_VERSION=2026.1.27-beta.1 \ -APP_BUILD="$(git rev-list --count HEAD)" \ -BUILD_CONFIG=release \ -SIGN_IDENTITY="Developer ID Application: ()" \ -scripts/package-mac-app.sh - -# 打包用于分发的 zip(包含资源分支以支持 Sparkle 增量更新) -ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.1.27-beta.1.zip - -# 可选:同时构建适合用户使用的样式化 DMG(拖拽到 /Applications) -scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.1.27-beta.1.dmg - -# 推荐:构建 + 公证/装订 zip + DMG -# 首先,创建一次钥匙串配置文件: -# xcrun notarytool store-credentials "openclaw-notary" \ -# --apple-id "" --team-id "" --password "" -NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \ -BUNDLE_ID=bot.molt.mac \ -APP_VERSION=2026.1.27-beta.1 \ -APP_BUILD="$(git rev-list --count HEAD)" \ -BUILD_CONFIG=release \ -SIGN_IDENTITY="Developer ID Application: ()" \ -scripts/package-mac-dist.sh - -# 可选:随发布一起提供 dSYM -ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.1.27-beta.1.dSYM.zip -``` - -## Appcast 条目 - -使用发布说明生成器,以便 Sparkle 渲染格式化的 HTML 说明: - -```bash -SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.1.27-beta.1.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml -``` - -从 `CHANGELOG.md`(通过 [`scripts/changelog-to-html.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/changelog-to-html.sh))生成 HTML 发布说明,并将其嵌入 appcast 条目。 -发布时,将更新后的 `appcast.xml` 与发布资源(zip + dSYM)一起提交。 - -## 发布与验证 - -- 将 `OpenClaw-2026.1.27-beta.1.zip`(和 `OpenClaw-2026.1.27-beta.1.dSYM.zip`)上传到标签 `v2026.1.27-beta.1` 对应的 GitHub 发布。 -- 确保原始 appcast URL 与内置的订阅源匹配:`https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`。 -- 完整性检查: - - `curl -I https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml` 返回 200。 - - `curl -I ` 在资源上传后返回 200。 - - 在之前的公开构建版本上,从 About 选项卡运行"Check for Updates…",验证 Sparkle 能正常安装新构建。 - -完成定义:已签名的应用 + appcast 已发布,从旧版本的更新流程正常工作,且发布资源已附加到 GitHub 发布。 diff --git a/docs/zh-CN/reference/RELEASING.md b/docs/zh-CN/reference/RELEASING.md index 81b0832f11c..cb1d02f60e8 100644 --- a/docs/zh-CN/reference/RELEASING.md +++ b/docs/zh-CN/reference/RELEASING.md @@ -1,123 +1,48 @@ --- read_when: - - 发布新的 npm 版本 - - 发布新的 macOS 应用版本 - - 发布前验证元数据 -summary: npm + macOS 应用的逐步发布清单 + - 查找公开发布渠道的定义 + - 查找版本命名与发布节奏 +summary: 公开发布渠道、版本命名与发布节奏 +title: 发布策略 x-i18n: - generated_at: "2026-02-03T10:09:28Z" - model: claude-opus-4-5 + generated_at: "2026-03-15T19:23:11Z" + model: claude-opus-4-6 provider: pi - source_hash: 1a684bc26665966eb3c9c816d58d18eead008fd710041181ece38c21c5ff1c62 + source_hash: df332d3169de7099661725d9266955456e80fc3d3ff95cb7aaf9997a02f0baaf source_path: reference/RELEASING.md workflow: 15 --- -# 发布清单(npm + macOS) +# 发布策略 -从仓库根目录使用 `pnpm`(Node 22+)。在打标签/发布前保持工作树干净。 +OpenClaw 有三个公开发布渠道: -## 操作员触发 +- stable:带标签的正式发布,发布到 npm `latest` +- beta:预发布标签,发布到 npm `beta` +- dev:`main` 分支的最新提交 -当操作员说"release"时,立即执行此预检(除非遇到阻碍否则不要额外提问): +## 版本命名 -- 阅读本文档和 `docs/platforms/mac/release.md`。 -- 从 `~/.profile` 加载环境变量并确认 `SPARKLE_PRIVATE_KEY_FILE` + App Store Connect 变量已设置(SPARKLE_PRIVATE_KEY_FILE 应位于 `~/.profile` 中)。 -- 如需要,使用 `~/Library/CloudStorage/Dropbox/Backup/Sparkle` 中的 Sparkle 密钥。 +- 正式发布版本号:`YYYY.M.D` + - Git 标签:`vYYYY.M.D` +- Beta 预发布版本号:`YYYY.M.D-beta.N` + - Git 标签:`vYYYY.M.D-beta.N` +- 月份和日期不补零 +- `latest` 表示当前 npm 正式发布版本 +- `beta` 表示当前 npm 预发布版本 +- Beta 版本可能会在 macOS 应用跟进之前发布 -1. **版本和元数据** +## 发布节奏 -- [ ] 更新 `package.json` 版本(例如 `2026.1.29`)。 -- [ ] 运行 `pnpm plugins:sync` 以对齐扩展包版本和变更日志。 -- [ ] 更新 CLI/版本字符串:[`src/cli/program.ts`](https://github.com/openclaw/openclaw/blob/main/src/cli/program.ts) 和 [`src/provider-web.ts`](https://github.com/openclaw/openclaw/blob/main/src/provider-web.ts) 中的 Baileys user agent。 -- [ ] 确认包元数据(name、description、repository、keywords、license)以及 `bin` 映射指向 [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) 作为 `openclaw`。 -- [ ] 如果依赖项有变化,运行 `pnpm install` 确保 `pnpm-lock.yaml` 是最新的。 +- 发布遵循 beta 优先原则 +- 仅在最新的 beta 版本验证通过后才会发布正式版本 +- 详细的发布流程、审批、凭证和恢复说明仅限维护者查阅 -2. **构建和产物** +## 公开参考 -- [ ] 如果 A2UI 输入有变化,运行 `pnpm canvas:a2ui:bundle` 并提交更新后的 [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/openclaw/openclaw/blob/main/src/canvas-host/a2ui/a2ui.bundle.js)。 -- [ ] `pnpm run build`(重新生成 `dist/`)。 -- [ ] 验证 npm 包的 `files` 包含所有必需的 `dist/*` 文件夹(特别是用于 headless node + ACP CLI 的 `dist/node-host/**` 和 `dist/acp/**`)。 -- [ ] 确认 `dist/build-info.json` 存在并包含预期的 `commit` 哈希(CLI 横幅在 npm 安装时使用此信息)。 -- [ ] 可选:构建后运行 `npm pack --pack-destination /tmp`;检查 tarball 内容并保留以备 GitHub 发布使用(**不要**提交它)。 +- [`.github/workflows/openclaw-npm-release.yml`](https://github.com/openclaw/openclaw/blob/main/.github/workflows/openclaw-npm-release.yml) +- [`scripts/openclaw-npm-release-check.ts`](https://github.com/openclaw/openclaw/blob/main/scripts/openclaw-npm-release-check.ts) -3. **变更日志和文档** - -- [ ] 更新 `CHANGELOG.md`,添加面向用户的亮点(如果文件不存在则创建);按版本严格降序排列条目。 -- [ ] 确保 README 示例/标志与当前 CLI 行为匹配(特别是新命令或选项)。 - -4. **验证** - -- [ ] `pnpm build` -- [ ] `pnpm check` -- [ ] `pnpm test`(如需覆盖率输出则使用 `pnpm test:coverage`) -- [ ] `pnpm release:check`(验证 npm pack 内容) -- [ ] `OPENCLAW_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke`(Docker 安装冒烟测试,快速路径;发布前必需) - - 如果已知上一个 npm 发布版本有问题,为预安装步骤设置 `OPENCLAW_INSTALL_SMOKE_PREVIOUS=` 或 `OPENCLAW_INSTALL_SMOKE_SKIP_PREVIOUS=1`。 -- [ ](可选)完整安装程序冒烟测试(添加非 root + CLI 覆盖):`pnpm test:install:smoke` -- [ ](可选)安装程序 E2E(Docker,运行 `curl -fsSL https://openclaw.ai/install.sh | bash`,新手引导,然后运行真实工具调用): - - `pnpm test:install:e2e:openai`(需要 `OPENAI_API_KEY`) - - `pnpm test:install:e2e:anthropic`(需要 `ANTHROPIC_API_KEY`) - - `pnpm test:install:e2e`(需要两个密钥;运行两个提供商) -- [ ](可选)如果你的更改影响发送/接收路径,抽查 Web Gateway 网关。 - -5. **macOS 应用(Sparkle)** - -- [ ] 构建并签名 macOS 应用,然后压缩以供分发。 -- [ ] 生成 Sparkle appcast(通过 [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh) 生成 HTML 注释)并更新 `appcast.xml`。 -- [ ] 保留应用 zip(和可选的 dSYM zip)以便附加到 GitHub 发布。 -- [ ] 按照 [macOS 发布](/platforms/mac/release) 获取确切命令和所需环境变量。 - - `APP_BUILD` 必须是数字且单调递增(不带 `-beta`),以便 Sparkle 正确比较版本。 - - 如果进行公证,使用从 App Store Connect API 环境变量创建的 `openclaw-notary` 钥匙串配置文件(参见 [macOS 发布](/platforms/mac/release))。 - -6. **发布(npm)** - -- [ ] 确认 git 状态干净;根据需要提交并推送。 -- [ ] 如需要,`npm login`(验证 2FA)。 -- [ ] `npm publish --access public`(预发布版本使用 `--tag beta`)。 -- [ ] 验证注册表:`npm view openclaw version`、`npm view openclaw dist-tags` 和 `npx -y openclaw@X.Y.Z --version`(或 `--help`)。 - -### 故障排除(来自 2.0.0-beta2 发布的笔记) - -- **npm pack/publish 挂起或产生巨大 tarball**:`dist/OpenClaw.app` 中的 macOS 应用包(和发布 zip)被扫入包中。通过 `package.json` 的 `files` 白名单发布内容来修复(包含 dist 子目录、docs、skills;排除应用包)。用 `npm pack --dry-run` 确认 `dist/OpenClaw.app` 未列出。 -- **npm auth dist-tags 的 Web 循环**:使用旧版认证以获取 OTP 提示: - - `NPM_CONFIG_AUTH_TYPE=legacy npm dist-tag add openclaw@X.Y.Z latest` -- **`npx` 验证失败并显示 `ECOMPROMISED: Lock compromised`**:使用新缓存重试: - - `NPM_CONFIG_CACHE=/tmp/npm-cache-$(date +%s) npx -y openclaw@X.Y.Z --version` -- **延迟修复后需要重新指向标签**:强制更新并推送标签,然后确保 GitHub 发布资产仍然匹配: - - `git tag -f vX.Y.Z && git push -f origin vX.Y.Z` - -7. **GitHub 发布 + appcast** - -- [ ] 打标签并推送:`git tag vX.Y.Z && git push origin vX.Y.Z`(或 `git push --tags`)。 -- [ ] 为 `vX.Y.Z` 创建/刷新 GitHub 发布,**标题为 `openclaw X.Y.Z`**(不仅仅是标签);正文应包含该版本的**完整**变更日志部分(亮点 + 更改 + 修复),内联显示(无裸链接),且**不得在正文中重复标题**。 -- [ ] 附加产物:`npm pack` tarball(可选)、`OpenClaw-X.Y.Z.zip` 和 `OpenClaw-X.Y.Z.dSYM.zip`(如果生成)。 -- [ ] 提交更新后的 `appcast.xml` 并推送(Sparkle 从 main 获取源)。 -- [ ] 从干净的临时目录(无 `package.json`),运行 `npx -y openclaw@X.Y.Z send --help` 确认安装/CLI 入口点正常工作。 -- [ ] 宣布/分享发布说明。 - -## 插件发布范围(npm) - -我们只发布 `@openclaw/*` 范围下的**现有 npm 插件**。不在 npm 上的内置插件保持**仅磁盘树**(仍在 `extensions/**` 中发布)。 - -获取列表的流程: - -1. `npm search @openclaw --json` 并捕获包名。 -2. 与 `extensions/*/package.json` 名称比较。 -3. 只发布**交集**(已在 npm 上)。 - -当前 npm 插件列表(根据需要更新): - -- @openclaw/bluebubbles -- @openclaw/diagnostics-otel -- @openclaw/discord -- @openclaw/lobster -- @openclaw/matrix -- @openclaw/msteams -- @openclaw/nextcloud-talk -- @openclaw/nostr -- @openclaw/voice-call -- @openclaw/zalo -- @openclaw/zalouser - -发布说明还必须标注**默认未启用**的**新可选内置插件**(例如:`tlon`)。 +维护者使用 +[`openclaw/maintainers/release/README.md`](https://github.com/openclaw/maintainers/blob/main/release/README.md) +中的私有发布文档作为实际操作手册。 diff --git a/docs/zh-CN/start/hubs.md b/docs/zh-CN/start/hubs.md index a2e6260fdf2..b303102dcc0 100644 --- a/docs/zh-CN/start/hubs.md +++ b/docs/zh-CN/start/hubs.md @@ -1,20 +1,24 @@ --- read_when: - 你想要一份完整的文档地图 -summary: 链接到每篇 OpenClaw 文档的导航中心 +summary: 链接到所有 OpenClaw 文档的导航中心 title: 文档导航中心 x-i18n: - generated_at: "2026-02-04T17:55:29Z" - model: claude-opus-4-5 + generated_at: "2026-03-15T19:29:16Z" + model: claude-opus-4-6 provider: pi - source_hash: c4b4572b64d36c9690988b8f964b0712f551ee6491b18a493701a17d2d352cb4 + source_hash: e12e8b7881311fdaf08cd297392911dfa30dc46031a7038b6bb9011d166b1669 source_path: start/hubs.md workflow: 15 --- # 文档导航中心 -使用这些导航中心发现每一个页面,包括深入解析和参考文档——它们不一定出现在左侧导航栏中。 + +如果你是 OpenClaw 新用户,请从[入门指南](/start/getting-started)开始。 + + +使用这些导航中心发现每一个页面,包括深入解析和参考文档——它们可能不会出现在左侧导航栏中。 ## 从这里开始 @@ -75,7 +79,6 @@ x-i18n: - [模型提供商中心](/providers/models) - [WhatsApp](/channels/whatsapp) - [Telegram](/channels/telegram) -- [Telegram(grammY 注意事项)](/channels/grammy) - [Slack](/channels/slack) - [Discord](/channels/discord) - [Mattermost](/channels/mattermost)(插件) @@ -113,17 +116,18 @@ x-i18n: - [OpenProse](/prose) - [CLI 参考](/cli) - [Exec 工具](/tools/exec) +- [PDF 工具](/tools/pdf) - [提权模式](/tools/elevated) - [定时任务](/automation/cron-jobs) - [定时任务 vs 心跳](/automation/cron-vs-heartbeat) - [思考 + 详细输出](/tools/thinking) - [模型](/concepts/models) - [子智能体](/tools/subagents) -- [Agent send CLI](/tools/agent-send) +- [智能体发送 CLI](/tools/agent-send) - [终端界面](/web/tui) - [浏览器控制](/tools/browser) - [浏览器(Linux 故障排除)](/tools/browser-linux-troubleshooting) -- [轮询](/automation/poll) +- [投票](/automation/poll) ## 节点、媒体、语音 @@ -160,7 +164,6 @@ x-i18n: - [macOS 权限](/platforms/mac/permissions) - [macOS 远程](/platforms/mac/remote) - [macOS 签名](/platforms/mac/signing) -- [macOS 发布](/platforms/mac/release) - [macOS Gateway 网关 (launchd)](/platforms/mac/bundled-gateway) - [macOS XPC](/platforms/mac/xpc) - [macOS Skills](/platforms/mac/skills) @@ -183,8 +186,6 @@ x-i18n: ## 实验(探索性) - [新手引导配置协议](/experiments/onboarding-config-protocol) -- [定时任务加固笔记](/experiments/plans/cron-add-hardening) -- [群组策略加固笔记](/experiments/plans/group-policy-hardening) - [研究:记忆](/experiments/research/memory) - [模型配置探索](/experiments/proposals/model-config) @@ -195,5 +196,5 @@ x-i18n: ## 测试 + 发布 - [测试](/reference/test) -- [发布检查清单](/reference/RELEASING) +- [发布策略](/reference/RELEASING) - [设备型号](/reference/device-models) diff --git a/package.json b/package.json index 2d880e80fe7..a839cdd3ec1 100644 --- a/package.json +++ b/package.json @@ -231,7 +231,7 @@ "build:strict-smoke": "pnpm canvas:a2ui:bundle && node scripts/tsdown-build.mjs && node scripts/copy-plugin-sdk-root-alias.mjs && pnpm build:plugin-sdk:dts", "canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh", "check": "pnpm check:host-env-policy:swift && pnpm format:check && pnpm tsgo && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:plugins:no-monolithic-plugin-sdk-entry-imports && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope", - "check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-links", + "check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-i18n-glossary && pnpm docs:check-links", "check:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --check", "check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500", "config:docs:check": "node --import tsx scripts/generate-config-doc-baseline.ts --check", @@ -246,6 +246,7 @@ "deadcode:ts-unused": "pnpm dlx ts-unused-exports tsconfig.json --ignoreTestFiles --exitWithCount", "dev": "node scripts/run-node.mjs", "docs:bin": "node scripts/build-docs-list.mjs", + "docs:check-i18n-glossary": "node scripts/check-docs-i18n-glossary.mjs", "docs:check-links": "node scripts/docs-link-audit.mjs", "docs:dev": "cd docs && mint dev", "docs:list": "node scripts/docs-list.js", diff --git a/scripts/check-docs-i18n-glossary.mjs b/scripts/check-docs-i18n-glossary.mjs new file mode 100644 index 00000000000..96f890bc4ff --- /dev/null +++ b/scripts/check-docs-i18n-glossary.mjs @@ -0,0 +1,237 @@ +#!/usr/bin/env node + +import { execFileSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const ROOT = process.cwd(); +const GLOSSARY_PATH = path.join(ROOT, "docs", ".i18n", "glossary.zh-CN.json"); +const DOC_FILE_RE = /^docs\/(?!zh-CN\/).+\.(md|mdx)$/i; +const LIST_ITEM_LINK_RE = /^\s*(?:[-*]|\d+\.)\s+\[([^\]]+)\]\((\/[^)]+)\)/; +const MAX_TITLE_WORDS = 8; +const MAX_LABEL_WORDS = 6; +const MAX_TERM_LENGTH = 80; + +/** + * @typedef {{ + * file: string; + * line: number; + * kind: "title" | "link label"; + * term: string; + * }} TermMatch + */ + +function parseArgs(argv) { + /** @type {{ base: string; head: string }} */ + const args = { base: "", head: "" }; + for (let i = 0; i < argv.length; i += 1) { + if (argv[i] === "--base") { + args.base = argv[i + 1] ?? ""; + i += 1; + continue; + } + if (argv[i] === "--head") { + args.head = argv[i + 1] ?? ""; + i += 1; + } + } + return args; +} + +function runGit(args) { + return execFileSync("git", args, { + cwd: ROOT, + stdio: ["ignore", "pipe", "pipe"], + encoding: "utf8", + }).trim(); +} + +function resolveBase(explicitBase) { + if (explicitBase) { + return explicitBase; + } + + const envBase = process.env.DOCS_I18N_GLOSSARY_BASE?.trim(); + if (envBase) { + return envBase; + } + + for (const candidate of ["origin/main", "fork/main", "main"]) { + try { + return runGit(["merge-base", candidate, "HEAD"]); + } catch { + // Try the next candidate. + } + } + + return ""; +} + +function listChangedDocs(base, head) { + const args = ["diff", "--name-only", "--diff-filter=ACMR", base]; + if (head) { + args.push(head); + } + args.push("--", "docs"); + + return runGit(args) + .split("\n") + .map((line) => line.trim()) + .filter((line) => DOC_FILE_RE.test(line)); +} + +function loadGlossarySources() { + const data = fs.readFileSync(GLOSSARY_PATH, "utf8"); + const entries = JSON.parse(data); + return new Set(entries.map((entry) => String(entry.source || "").trim()).filter(Boolean)); +} + +function containsLatin(text) { + return /[A-Za-z]/.test(text); +} + +function wordCount(text) { + return text.trim().split(/\s+/).filter(Boolean).length; +} + +function unquoteScalar(raw) { + const value = raw.trim(); + if ( + (value.startsWith('"') && value.endsWith('"')) || + (value.startsWith("'") && value.endsWith("'")) + ) { + return value.slice(1, -1).trim(); + } + return value; +} + +function isGlossaryCandidate(term, maxWords) { + if (!term) { + return false; + } + if (!containsLatin(term)) { + return false; + } + if (term.includes("`")) { + return false; + } + if (term.length > MAX_TERM_LENGTH) { + return false; + } + return wordCount(term) <= maxWords; +} + +function readGitFile(base, relPath) { + try { + return runGit(["show", `${base}:${relPath}`]); + } catch { + return ""; + } +} + +/** + * @param {string} file + * @param {string} text + * @returns {Map} + */ +function extractTerms(file, text) { + /** @type {Map} */ + const terms = new Map(); + const lines = text.split("\n"); + + if (lines[0]?.trim() === "---") { + for (let index = 1; index < lines.length; index += 1) { + const line = lines[index]; + if (line.trim() === "---") { + break; + } + + const match = line.match(/^title:\s*(.+)\s*$/); + if (!match) { + continue; + } + + const title = unquoteScalar(match[1]); + if (isGlossaryCandidate(title, MAX_TITLE_WORDS)) { + terms.set(title, { file, line: index + 1, kind: "title", term: title }); + } + break; + } + } + + for (let index = 0; index < lines.length; index += 1) { + const match = lines[index].match(LIST_ITEM_LINK_RE); + if (!match) { + continue; + } + + const label = match[1].trim(); + if (!isGlossaryCandidate(label, MAX_LABEL_WORDS)) { + continue; + } + + if (!terms.has(label)) { + terms.set(label, { file, line: index + 1, kind: "link label", term: label }); + } + } + + return terms; +} + +function main() { + const args = parseArgs(process.argv.slice(2)); + const base = resolveBase(args.base); + + if (!base) { + console.warn( + "docs:check-i18n-glossary: no merge base found; skipping glossary coverage check.", + ); + process.exit(0); + } + + const changedDocs = listChangedDocs(base, args.head); + if (changedDocs.length === 0) { + process.exit(0); + } + + const glossary = loadGlossarySources(); + /** @type {TermMatch[]} */ + const missing = []; + + for (const relPath of changedDocs) { + const absPath = path.join(ROOT, relPath); + if (!fs.existsSync(absPath)) { + continue; + } + + const currentTerms = extractTerms(relPath, fs.readFileSync(absPath, "utf8")); + const baseTerms = extractTerms(relPath, readGitFile(base, relPath)); + + for (const [term, match] of currentTerms) { + if (baseTerms.has(term)) { + continue; + } + if (glossary.has(term)) { + continue; + } + missing.push(match); + } + } + + if (missing.length === 0) { + process.exit(0); + } + + console.error("docs:check-i18n-glossary: missing zh-CN glossary entries for changed doc labels:"); + for (const match of missing) { + console.error(`- ${match.file}:${match.line} ${match.kind} "${match.term}"`); + } + console.error(""); + console.error( + "Add exact source terms to docs/.i18n/glossary.zh-CN.json before rerunning docs-i18n.", + ); + console.error(`Checked changed English docs relative to ${base}.`); + process.exit(1); +} + +main(); diff --git a/scripts/docs-i18n/prompt.go b/scripts/docs-i18n/prompt.go index 8ecf8688140..773dfd8fcfd 100644 --- a/scripts/docs-i18n/prompt.go +++ b/scripts/docs-i18n/prompt.go @@ -58,6 +58,11 @@ Rules: - Do not remove, reorder, or summarize content. - Use fluent, idiomatic technical Chinese; avoid slang or jokes. - Use neutral documentation tone; prefer “你/你的”, avoid “您/您的”. +- Glossary terms are mandatory. When a source term matches a glossary entry, use + the glossary target exactly, including headings, link labels, and short + UI-style labels. +- If a glossary target is identical to the source text, preserve that term in + English exactly as written. - Insert a space between Latin characters and CJK text (W3C CLREQ), e.g., “Gateway 网关”, “Skills 配置”. - Use Chinese quotation marks “ and ” for Chinese prose; keep ASCII quotes inside code spans/blocks or literal CLI/keys. - Keep product names in English: OpenClaw, Pi, WhatsApp, Telegram, Discord, iMessage, Slack, Microsoft Teams, Google Chat, Signal. @@ -90,6 +95,11 @@ Rules: - Do not remove, reorder, or summarize content. - Use fluent, idiomatic technical Japanese; avoid slang or jokes. - Use neutral documentation tone; avoid overly formal honorifics (e.g., avoid “〜でございます”). +- Glossary terms are mandatory. When a source term matches a glossary entry, use + the glossary target exactly, including headings, link labels, and short + UI-style labels. +- If a glossary target is identical to the source text, preserve that term in + English exactly as written. - Use Japanese quotation marks 「 and 」 for Japanese prose; keep ASCII quotes inside code spans/blocks or literal CLI/keys. - Do not add or remove spacing around Latin text just because it borders Japanese; keep spacing stable unless required by Japanese grammar. - Keep product names in English: OpenClaw, Pi, WhatsApp, Telegram, Discord, iMessage, Slack, Microsoft Teams, Google Chat, Signal. @@ -121,6 +131,11 @@ Rules: - Do not remove, reorder, or summarize content. - Use fluent, idiomatic technical language in the target language; avoid slang or jokes. - Use neutral documentation tone. +- Glossary terms are mandatory. When a source term matches a glossary entry, use + the glossary target exactly, including headings, link labels, and short + UI-style labels. +- If a glossary target is identical to the source text, preserve that term in + English exactly as written. - Keep product names in English: OpenClaw, Pi, WhatsApp, Telegram, Discord, iMessage, Slack, Microsoft Teams, Google Chat, Signal. - Keep these terms in English: Skills, local loopback, Tailscale. - Never output an empty response; if unsure, return the source text unchanged. @@ -135,7 +150,7 @@ func buildGlossaryPrompt(glossary []GlossaryEntry) string { return "" } var lines []string - lines = append(lines, "Preferred translations (use when natural):") + lines = append(lines, "Required terminology (use exactly when the source term matches):") for _, entry := range glossary { if entry.Source == "" || entry.Target == "" { continue