From 44120e28b61bdb95da8c8b09ce89dc520941e8fd Mon Sep 17 00:00:00 2001 From: kite Date: Fri, 22 May 2026 15:30:49 +0800 Subject: [PATCH] fix: fix some bug --- .claude/commands/open-code-review.md | 33 +++++ .github/workflows/release.yml | 3 + .gitignore | 4 +- bin/ocr.js | 27 +++- package.json | 2 +- scripts/install.js | 20 ++- scripts/publish/.env.example | 10 +- scripts/publish/publish.sh | 57 ++------ scripts/update.js | 199 +++++++++++++++++++++++++++ 9 files changed, 298 insertions(+), 57 deletions(-) create mode 100644 .claude/commands/open-code-review.md create mode 100644 scripts/update.js diff --git a/.claude/commands/open-code-review.md b/.claude/commands/open-code-review.md new file mode 100644 index 0000000..a58cb58 --- /dev/null +++ b/.claude/commands/open-code-review.md @@ -0,0 +1,33 @@ +# /open-code-review + +Invoke the professional code review Agent CLI tool OpenCodeReview (OCR) to review current code changes, and let the Agent autonomously decide whether to apply fixes. + +## Workflow + +### Step 1: Run Code Review + +Run the OCR command: + +```bash +ocr review --audience agent [user-args] +``` +- Default (no user arguments): reviews staged, unstaged, and untracked changes (workspace mode). +- If the user provides `--commit` or `--c`: pass through as-is. +- If the user provides `--from` and `--to`: pass through as-is. +- (Optional) Provide `--background "requirement context"` to review whether the requirements are correctly implemented. +- Capture full stdout. Set a 5-minute timeout. +- If the `ocr` command is not found, install it by running `npm i -g @alibaba-group/open-code-review`. + +### Step 2: Filter and Evaluate + +For each comment, assess its validity and quality: + +- **High**: Obvious bugs, security issues, clear mistakes, or well-founded suggestions with precise fix proposals +- **Medium**: Reasonable concerns but context-dependent, style/performance suggestions, or fixes that require manual implementation +- **Low**: Likely false positives, lacking sufficient context, nitpicks, or meaningless suggestions + +Silently discard low-confidence comments. Display the remaining comments. + +### Step 3: Fix + +Automatically fix issues and suggestions that are worth adopting. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c044b58..36823b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -77,9 +77,12 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20 + registry-url: 'https://registry.npmjs.org' - name: Inject version from tag run: jq --arg v "${GITHUB_REF_NAME#v}" '.version = $v' package.json > package.json.tmp && mv package.json.tmp package.json - name: Publish to npm run: npm publish --access public --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 91ce8dd..7d4604c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ *.iws dist/ -.claude/ +.claude/plans +.claude/settings.local.json + CLAUDE.md pages/package-lock.json scripts/hooks/ diff --git a/bin/ocr.js b/bin/ocr.js index 6389689..807fc16 100755 --- a/bin/ocr.js +++ b/bin/ocr.js @@ -1,11 +1,36 @@ #!/usr/bin/env node "use strict"; -const { spawnSync } = require("child_process"); +const { spawnSync, spawn } = require("child_process"); const path = require("path"); +const fs = require("fs"); +const os = require("os"); const binaryPath = path.join(__dirname, "opencodereview"); +if (!process.env.OCR_NO_UPDATE) { + const stateDir = path.join(os.homedir(), ".open-code-review"); + const tsFile = path.join(stateDir, "last-update-check"); + const cooldownMs = + (parseInt(process.env.OCR_UPDATE_INTERVAL, 10) || 60) * 60 * 1000; + + let shouldCheck = true; + try { + const mt = fs.statSync(tsFile).mtimeMs; + if (Date.now() - mt < cooldownMs) shouldCheck = false; + } catch (_) {} + + if (shouldCheck) { + const updateScript = path.join(__dirname, "..", "scripts", "update.js"); + const child = spawn(process.execPath, [updateScript], { + detached: true, + stdio: "ignore", + env: Object.assign({}, process.env, { OCR_NO_UPDATE: "1" }), + }); + child.unref(); + } +} + const result = spawnSync(binaryPath, process.argv.slice(2), { stdio: "inherit", env: process.env, diff --git a/package.json b/package.json index 038255a..b8cc409 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@alibaba-group/open-code-review", - "version": "1.0.0", + "version": "0.0.0", "description": "OpenCodeReview CLI — AI-powered code review tool", "bin": { "ocr": "bin/ocr.js" diff --git a/scripts/install.js b/scripts/install.js index 92bee7a..88f8ef8 100644 --- a/scripts/install.js +++ b/scripts/install.js @@ -218,7 +218,19 @@ async function main() { info(" ocr review Start a code review"); } -main().catch((err) => { - error(err.message); - process.exit(1); -}); +if (require.main === module) { + main().catch((err) => { + error(err.message); + process.exit(1); + }); +} else { + module.exports = { + detectPlatform, + loadPackageJson, + buildUrl, + download, + downloadText, + downloadBinary, + computeChecksum, + }; +} diff --git a/scripts/publish/.env.example b/scripts/publish/.env.example index b360b65..81fe4be 100644 --- a/scripts/publish/.env.example +++ b/scripts/publish/.env.example @@ -5,16 +5,16 @@ # source scripts/publish/.env.internal && ./scripts/publish/publish.sh # Override package name -OCR_PKG_NAME= +export OCR_PKG_NAME= # npm registry URL -OCR_PUBLISH_REGISTRY= +export OCR_PUBLISH_REGISTRY= # Binary download URL template (supports {version} {os} {arch} placeholders) -OCR_URL_PATTERN= +export OCR_URL_PATTERN= # Checksum file URL template -OCR_CHECKSUM_PATTERN= +export OCR_CHECKSUM_PATTERN= # [Optional] Git repo for binary uploads (set to enable upload step) -OCR_RELEASE_GIT_REPO= +export OCR_RELEASE_GIT_REPO= diff --git a/scripts/publish/publish.sh b/scripts/publish/publish.sh index 6badd87..9e2c092 100755 --- a/scripts/publish/publish.sh +++ b/scripts/publish/publish.sh @@ -100,24 +100,7 @@ else "make -C \"$PROJECT_ROOT\" dist" fi -# ── Sync package.json version ───────────────────────────────────────────────── -sync_version() { - local pkg="$PROJECT_ROOT/package.json" - local current - current=$(jq -r '.version' "$pkg") - if [ "$NPM_VERSION" = "$current" ]; then - info "package.json version already matches ${NPM_VERSION}." - else - info "Updating package.json version: ${current} → ${NPM_VERSION}" - local tmp - tmp=$(mktemp) - jq --arg v "$NPM_VERSION" '.version = $v' "$pkg" > "$tmp" - mv "$tmp" "$pkg" - success "package.json version updated" - fi -} - -run_step "Syncing version" "sync_version" +# ── (version is injected temporarily during patch_package_json below) ──────── # ── Upload to git-based release repo (optional) ────────────────────────────── upload_to_git_repo() { @@ -189,51 +172,44 @@ else fi # ── Patch package.json for publish ──────────────────────────────────────────── -PACKAGE_PATCHED=0 PACKAGE_BACKUP="" patch_package_json() { local pkg="$PROJECT_ROOT/package.json" - local changed=0 + PACKAGE_BACKUP=$(mktemp) + cp "$pkg" "$PACKAGE_BACKUP" + local tmp tmp=$(mktemp) cp "$pkg" "$tmp" + # Always inject version from git tag (temporary, not committed) + jq --arg v "$NPM_VERSION" '.version = $v' "$tmp" > "${tmp}.new" && mv "${tmp}.new" "$tmp" + info " version → ${NPM_VERSION}" + if [ -n "${OCR_PKG_NAME:-}" ]; then jq --arg n "$OCR_PKG_NAME" '.name = $n' "$tmp" > "${tmp}.new" && mv "${tmp}.new" "$tmp" info " name → ${OCR_PKG_NAME}" - changed=1 fi if [ -n "${OCR_PUBLISH_REGISTRY:-}" ]; then jq --arg r "$OCR_PUBLISH_REGISTRY" '.publishConfig.registry = $r | .publishConfig.access = "public"' "$tmp" > "${tmp}.new" && mv "${tmp}.new" "$tmp" info " publishConfig.registry → ${OCR_PUBLISH_REGISTRY}" - changed=1 fi if [ -n "${OCR_URL_PATTERN:-}" ]; then jq --arg u "$OCR_URL_PATTERN" '.ocrConfig.urlPattern = $u' "$tmp" > "${tmp}.new" && mv "${tmp}.new" "$tmp" info " ocrConfig.urlPattern → ${OCR_URL_PATTERN}" - changed=1 fi if [ -n "${OCR_CHECKSUM_PATTERN:-}" ]; then jq --arg c "$OCR_CHECKSUM_PATTERN" '.ocrConfig.checksumPattern = $c' "$tmp" > "${tmp}.new" && mv "${tmp}.new" "$tmp" info " ocrConfig.checksumPattern → ${OCR_CHECKSUM_PATTERN}" - changed=1 fi - if [ "$changed" -eq 1 ]; then - PACKAGE_BACKUP=$(mktemp) - cp "$pkg" "$PACKAGE_BACKUP" - mv "$tmp" "$pkg" - PACKAGE_PATCHED=1 - success "package.json patched for publish" - else - rm -f "$tmp" - info "No overrides configured, publishing with default package.json" - fi + mv "$tmp" "$pkg" + success "package.json patched for publish" } run_step "Patching package.json" "patch_package_json" @@ -270,22 +246,13 @@ else fi # ── Restore package.json ───────────────────────────────────────────────────── -if [ "$PACKAGE_PATCHED" -eq 1 ] && [ -n "$PACKAGE_BACKUP" ]; then +if [ -n "$PACKAGE_BACKUP" ]; then cp "$PACKAGE_BACKUP" "$PROJECT_ROOT/package.json" rm -f "$PACKAGE_BACKUP" - success "package.json restored (version bump preserved)" + success "package.json restored" echo "" fi -# ── Commit version bump (if version was synced) ────────────────────────────── -git add package.json 2>/dev/null || true -if ! git diff --cached --quiet 2>/dev/null; then - git commit -m "chore(release): bump version to ${VERSION_TAG}" - success "Committed version bump" -else - info "No version changes to commit." -fi - # ── Done ────────────────────────────────────────────────────────────────────── echo "" echo -e "${GREEN}${BOLD}============================================${RESET}" diff --git a/scripts/update.js b/scripts/update.js new file mode 100644 index 0000000..b37f3c1 --- /dev/null +++ b/scripts/update.js @@ -0,0 +1,199 @@ +#!/usr/bin/env node +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const os = require("os"); +const https = require("https"); +const { spawnSync } = require("child_process"); + +const { + detectPlatform, + loadPackageJson, + buildUrl, + downloadText, + downloadBinary, + computeChecksum, +} = require("./install.js"); + +const packageRoot = path.join(__dirname, ".."); +const binDir = path.join(packageRoot, "bin"); +const binaryPath = path.join(binDir, "opencodereview"); +const stateDir = path.join(os.homedir(), ".open-code-review"); +const tsFile = path.join(stateDir, "last-update-check"); +const lockFile = path.join(stateDir, "update.lock"); + +const GITHUB_API_URL = + "https://api.github.com/repos/alibaba/open-code-review/releases/latest"; + +function touchTimestamp() { + fs.mkdirSync(stateDir, { recursive: true }); + const now = new Date(); + try { + fs.utimesSync(tsFile, now, now); + } catch (_) { + fs.writeFileSync(tsFile, now.toISOString()); + } +} + +function acquireLock() { + fs.mkdirSync(stateDir, { recursive: true }); + try { + fs.writeFileSync(lockFile, String(process.pid), { flag: "wx" }); + return true; + } catch (e) { + if (e.code !== "EEXIST") return false; + try { + const pid = parseInt(fs.readFileSync(lockFile, "utf8").trim(), 10); + process.kill(pid, 0); + return false; + } catch (_) { + try { + fs.unlinkSync(lockFile); + fs.writeFileSync(lockFile, String(process.pid), { flag: "wx" }); + return true; + } catch (_2) { + return false; + } + } + } +} + +function releaseLock() { + try { + fs.unlinkSync(lockFile); + } catch (_) {} +} + +function getInstalledVersion() { + try { + const result = spawnSync(binaryPath, ["version"], { + encoding: "utf8", + timeout: 3000, + }); + const match = (result.stdout || "").match(/v(\d+\.\d+(?:\.\d+)?)/); + return match ? match[1] : null; + } catch (_) { + return null; + } +} + +function fetchLatestVersion() { + return new Promise((resolve) => { + const options = { + headers: { + "User-Agent": "ocr-updater", + Accept: "application/vnd.github.v3+json", + }, + timeout: 15000, + }; + const req = https + .get(GITHUB_API_URL, options, (res) => { + if (res.statusCode !== 200) { + res.resume(); + resolve(null); + return; + } + let data = ""; + res.on("data", (chunk) => (data += chunk)); + res.on("end", () => { + try { + const json = JSON.parse(data); + const tag = json.tag_name || ""; + const version = tag.startsWith("v") ? tag.slice(1) : tag; + resolve(version || null); + } catch (_) { + resolve(null); + } + }); + res.on("error", () => resolve(null)); + }) + .on("error", () => resolve(null)); + req.on("timeout", () => { + req.destroy(); + resolve(null); + }); + }); +} + +function semverGt(a, b) { + const pa = a.split(".").map(Number); + const pb = b.split(".").map(Number); + for (let i = 0; i < 3; i++) { + if ((pa[i] || 0) > (pb[i] || 0)) return true; + if ((pa[i] || 0) < (pb[i] || 0)) return false; + } + return false; +} + +function cleanupTemp() { + try { + const files = fs.readdirSync(binDir); + for (const f of files) { + if (f.startsWith(".opencodereview.tmp.")) { + fs.unlinkSync(path.join(binDir, f)); + } + } + } catch (_) {} +} + +async function main() { + touchTimestamp(); + + if (!acquireLock()) return; + + cleanupTemp(); + + try { + const installedVersion = getInstalledVersion(); + if (!installedVersion) return; + + const latestVersion = await fetchLatestVersion(); + if (!latestVersion) return; + + if (!semverGt(latestVersion, installedVersion)) return; + + const { os: platform, arch } = detectPlatform(); + const pkg = loadPackageJson(); + const config = pkg.ocrConfig; + + const vars = { version: latestVersion, os: platform, arch }; + const downloadUrl = buildUrl(config.urlPattern, vars); + + const tempPath = path.join(binDir, `.opencodereview.tmp.${process.pid}`); + await downloadBinary(downloadUrl, tempPath); + fs.chmodSync(tempPath, 0o755); + + if (config.checksumPattern) { + try { + const checksumUrl = buildUrl(config.checksumPattern, vars); + const shaContent = await downloadText(checksumUrl); + const actualSha = await computeChecksum(tempPath); + + let verified = false; + for (const line of shaContent.split("\n")) { + const trimmed = line.trim(); + if (trimmed.includes(`-${platform}-${arch}`)) { + const expectedSha = trimmed.split(/\s+/)[0].toLowerCase(); + if (expectedSha && actualSha !== expectedSha) { + fs.unlinkSync(tempPath); + return; + } + verified = true; + break; + } + } + } catch (_) { + // checksum fetch failed, continue with the download + } + } + + fs.renameSync(tempPath, binaryPath); + } catch (_) { + cleanupTemp(); + } finally { + releaseLock(); + } +} + +main().catch(() => {});