Fix/readme-implementation-sync#7
Conversation
ccsert
commented
Jan 31, 2026
- Add incremental review and structured tags to features list
- Update project structure with complete tool list (5 tools)
- Add skills/ directory and gitea-assistant agent to structure
- Clarify that .gitea/workflows/ is created after installation
- Remove unused AUTO_APPROVE env var from Dockerfile
- Update copilot-instructions.md with all available tools
- Add gitea-incremental-diff tool for reviewing only new changes since last review - Add structured review tags (BUG, SECURITY, PERFORMANCE, STYLE, DOCS, TEST) - Add severity levels (CRITICAL, HIGH, MEDIUM, LOW) - Add unit tests for new features - Update code-review agent to support incremental reviews
- Add incremental review and structured tags to features list - Update project structure with complete tool list (5 tools) - Add skills/ directory and gitea-assistant agent to structure - Clarify that .gitea/workflows/ is created after installation - Remove unused AUTO_APPROVE env var from Dockerfile - Update copilot-instructions.md with all available tools
There was a problem hiding this comment.
Pull request overview
This PR synchronizes documentation with the implementation of new incremental review and structured tagging features for the OpenCode Gitea Review tool.
Changes:
- Added documentation for incremental review feature and structured tags to README files
- Removed unused AUTO_APPROVE environment variable from Dockerfile
- Updated tool documentation and agent instructions with complete tool list and structured tag format
- Added three new test files for validating review tags, statistics, and incremental diff functionality
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Added incremental review and structured tags features; updated project structure with complete 5-tool list and skills/ directory |
| README_zh.md | Chinese translation of the same README.md changes |
| Dockerfile | Removed unused AUTO_APPROVE environment variable |
| .opencode-review/tools/gitea-review.txt | Comprehensive documentation update with summary format guidelines and structured tag examples |
| .opencode-review/tools/gitea-review.ts | Added structured tag constants, helper functions (formatReviewTag, parseReviewTag, formatCommentBody), and suggestion support |
| .opencode-review/tools/gitea-incremental-diff.txt | New documentation for incremental diff feature |
| .opencode-review/tools/gitea-incremental-diff.ts | New tool implementation for fetching only changes since last review |
| .opencode-review/tests/gitea-review-tags.test.ts | New test file for structured tag formatting and parsing |
| .opencode-review/tests/gitea-review-stats.test.ts | New test file for review statistics (references non-existent implementation) |
| .opencode-review/tests/gitea-incremental-diff.test.ts | New test file for incremental diff helper functions |
| .opencode-review/agents/code-review.md | Updated agent instructions with structured tags, auto-fix suggestions, and incremental review workflow |
| .github/copilot-instructions.md | Updated with complete tool list and structured tag format |
| // 从 gitea-incremental-diff.ts 复制的函数 | ||
| function findLastReviewedCommit(reviews: Review[], commits: Commit[]): string | null { | ||
| if (!reviews || reviews.length === 0) return null | ||
|
|
||
| const commitShas = new Set(commits.map(c => c.sha)) | ||
|
|
||
| const validReviews = reviews | ||
| .filter(r => r.commit_id && commitShas.has(r.commit_id)) | ||
| .sort((a, b) => new Date(b.submitted_at).getTime() - new Date(a.submitted_at).getTime()) | ||
|
|
||
| return validReviews.length > 0 ? validReviews[0].commit_id : null | ||
| } | ||
|
|
||
| function findCommitsAfter(commits: Commit[], afterSha: string | null): Commit[] { | ||
| if (!afterSha) return commits | ||
|
|
||
| const afterIndex = commits.findIndex(c => c.sha === afterSha) | ||
| if (afterIndex === -1) { | ||
| return commits | ||
| } | ||
|
|
||
| return commits.slice(afterIndex + 1) | ||
| } |
There was a problem hiding this comment.
The test file duplicates function implementations instead of importing them from gitea-incremental-diff.ts. These functions (findLastReviewedCommit, findCommitsAfter) are defined in the source file but not exported. Either export them from the source file and import them here, or keep them as internal implementation details and test the tool's public interface instead. Testing copied code doesn't validate the actual implementation.
| * Parse a review comment to extract category and severity | ||
| */ | ||
| export function parseReviewTag(body: string): { category?: ReviewCategory; severity?: ReviewSeverity } | null { | ||
| const match = body.match(/\*?\*?\[([A-Z]+):([A-Z]+)\]\*?\*?/) |
There was a problem hiding this comment.
The regex pattern allows optional asterisks in flexible positions (e.g., it would match "[BUG:HIGH]" or "[BUG:HIGH]"). This could lead to inconsistent parsing. Consider using a stricter pattern like /^\*\*\[([A-Z]+):([A-Z]+)\]\*\*$/ to only match properly formatted tags, or at minimum use \*{0,2} to ensure matched asterisks are properly paired.
| const match = body.match(/\*?\*?\[([A-Z]+):([A-Z]+)\]\*?\*?/) | |
| const match = body.match(/\*{0,2}\[([A-Z]+):([A-Z]+)\]\*{0,2}/) |
| - � **Incremental Review** - Only reviews new changes since last review (for updated PRs) | ||
| - 🏷️ **Structured Tags** - Categorizes issues by type (BUG, SECURITY, PERFORMANCE) and severity | ||
| - �🐳 **Docker Support** - Zero-config installation with pre-built image |
There was a problem hiding this comment.
The emoji characters are not displaying correctly in the feature list. Line 17 shows "� Incremental Review" and line 19 shows "�🐳 Docker Support" with corrupted characters. This will make the README look unprofessional and harder to read. Ensure proper UTF-8 encoding is used and valid emoji code points are inserted.
| - � **Incremental Review** - Only reviews new changes since last review (for updated PRs) | |
| - 🏷️ **Structured Tags** - Categorizes issues by type (BUG, SECURITY, PERFORMANCE) and severity | |
| - �🐳 **Docker Support** - Zero-config installation with pre-built image | |
| - 🔁 **Incremental Review** - Only reviews new changes since last review (for updated PRs) | |
| - 🏷️ **Structured Tags** - Categorizes issues by type (BUG, SECURITY, PERFORMANCE) and severity | |
| - 🐳 **Docker Support** - Zero-config installation with pre-built image |
| - � **增量审查** - 仅审查上次审查后的新变更(适用于 PR 更新) | ||
| - 🏷️ **结构化标签** - 按类型(BUG、SECURITY、PERFORMANCE)和严重程度分类问题 | ||
| - �🐳 **Docker 支持** - 预构建镜像,零配置安装 |
There was a problem hiding this comment.
The emoji characters are not displaying correctly in the feature list. Line 17 shows "� 增量审查" and line 19 shows "�🐳 Docker 支持" with corrupted characters. This will make the README look unprofessional and harder to read. Ensure proper UTF-8 encoding is used and valid emoji code points are inserted.
| - � **增量审查** - 仅审查上次审查后的新变更(适用于 PR 更新) | |
| - 🏷️ **结构化标签** - 按类型(BUG、SECURITY、PERFORMANCE)和严重程度分类问题 | |
| - �🐳 **Docker 支持** - 预构建镜像,零配置安装 | |
| - 📈 **增量审查** - 仅审查上次审查后的新变更(适用于 PR 更新) | |
| - 🏷️ **结构化标签** - 按类型(BUG、SECURITY、PERFORMANCE)和严重程度分类问题 | |
| - 🐳 **Docker 支持** - 预构建镜像,零配置安装 |
| // Identify bot reviews by checking for our review marker | ||
| const BOT_REVIEW_MARKER = "<!-- opencode-review -->" | ||
|
|
||
| function isBotReview(review: Review): boolean { | ||
| // Check if review was made by a bot-like user or contains our marker | ||
| // Since we can't easily identify our own reviews, we look for any APPROVED or REQUEST_CHANGES | ||
| // reviews that have a commit_id | ||
| return Boolean(review.commit_id && review.commit_id.length > 0 && | ||
| (review.state === "APPROVED" || review.state === "REQUEST_CHANGES" || review.state === "COMMENT")) |
There was a problem hiding this comment.
The constant BOT_REVIEW_MARKER is defined but never used. The isBotReview function doesn't reference it, instead just checking the review state and commit_id. Either this marker should be used to identify bot reviews (e.g., by checking review body content), or it should be removed to avoid confusion.
| // Identify bot reviews by checking for our review marker | |
| const BOT_REVIEW_MARKER = "<!-- opencode-review -->" | |
| function isBotReview(review: Review): boolean { | |
| // Check if review was made by a bot-like user or contains our marker | |
| // Since we can't easily identify our own reviews, we look for any APPROVED or REQUEST_CHANGES | |
| // reviews that have a commit_id | |
| return Boolean(review.commit_id && review.commit_id.length > 0 && | |
| (review.state === "APPROVED" || review.state === "REQUEST_CHANGES" || review.state === "COMMENT")) | |
| // Heuristic to determine which reviews should be considered when finding the last reviewed commit. | |
| // Since we can't easily identify our own reviews, we look for any APPROVED, REQUEST_CHANGES, or COMMENT | |
| // reviews that have a non-empty commit_id. | |
| function isBotReview(review: Review): boolean { | |
| return Boolean( | |
| review.commit_id && | |
| review.commit_id.length > 0 && | |
| (review.state === "APPROVED" || review.state === "REQUEST_CHANGES" || review.state === "COMMENT") | |
| ) |
| // 复制自 gitea-review.ts 的常量和函数 | ||
| const REVIEW_CATEGORIES = ["BUG", "SECURITY", "PERFORMANCE", "STYLE", "DOCS", "TEST"] as const | ||
| const REVIEW_SEVERITIES = ["CRITICAL", "HIGH", "MEDIUM", "LOW"] as const | ||
|
|
||
| type ReviewCategory = typeof REVIEW_CATEGORIES[number] | ||
| type ReviewSeverity = typeof REVIEW_SEVERITIES[number] | ||
|
|
||
| function formatReviewTag(category: ReviewCategory, severity: ReviewSeverity): string { | ||
| return `**[${category}:${severity}]**` | ||
| } | ||
|
|
||
| function parseReviewTag(body: string): { category?: ReviewCategory; severity?: ReviewSeverity } | null { | ||
| const match = body.match(/\*?\*?\[([A-Z]+):([A-Z]+)\]\*?\*?/) | ||
| if (!match) return null | ||
|
|
||
| const category = match[1] as ReviewCategory | ||
| const severity = match[2] as ReviewSeverity | ||
|
|
||
| if (REVIEW_CATEGORIES.includes(category) && REVIEW_SEVERITIES.includes(severity)) { | ||
| return { category, severity } | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| function formatCommentBody(body: string, suggestion?: string): string { | ||
| if (!suggestion) return body | ||
|
|
||
| const trimmedBody = body.trimEnd() | ||
|
|
||
| return `${trimmedBody} | ||
|
|
||
| \`\`\`suggestion | ||
| ${suggestion} | ||
| \`\`\` | ||
| ` | ||
| } |
There was a problem hiding this comment.
The test file duplicates function implementations instead of importing them from gitea-review.ts. The actual functions are exported from gitea-review.ts (e.g., formatReviewTag, parseReviewTag, formatCommentBody at lines 93-127) and should be imported here. This duplication means if the implementation changes, the tests will continue passing while testing outdated logic, defeating the purpose of automated testing.
| // 从 gitea-review-stats.ts 复制的类型和函数 | ||
| interface ReviewStats { | ||
| totalReviews: number | ||
| totalComments: number | ||
| byCategory: Record<ReviewCategory, number> | ||
| bySeverity: Record<ReviewSeverity, number> | ||
| byFile: Record<string, number> | ||
| byCategorySeverity: Record<string, number> | ||
| timeline: Array<{ date: string; count: number }> | ||
| suggestions: number | ||
| untaggedComments: number | ||
| issues: IssueRecord[] | ||
| } | ||
|
|
||
| function parseReviewTag(body: string): { category?: ReviewCategory; severity?: ReviewSeverity } | null { | ||
| const match = body.match(/\*?\*?\[([A-Z]+):([A-Z]+)\]\*?\*?/) | ||
| if (!match) return null | ||
|
|
||
| const category = match[1] as ReviewCategory | ||
| const severity = match[2] as ReviewSeverity | ||
|
|
||
| if (REVIEW_CATEGORIES.includes(category) && REVIEW_SEVERITIES.includes(severity)) { | ||
| return { category, severity } | ||
| } | ||
| return null | ||
| } | ||
|
|
||
| function initializeStats(): ReviewStats { | ||
| const byCategory = {} as Record<ReviewCategory, number> | ||
| const bySeverity = {} as Record<ReviewSeverity, number> | ||
|
|
||
| for (const cat of REVIEW_CATEGORIES) { | ||
| byCategory[cat] = 0 | ||
| } | ||
| for (const sev of REVIEW_SEVERITIES) { | ||
| bySeverity[sev] = 0 | ||
| } | ||
|
|
||
| return { | ||
| totalReviews: 0, | ||
| totalComments: 0, | ||
| byCategory, | ||
| bySeverity, | ||
| byFile: {}, | ||
| byCategorySeverity: {}, | ||
| timeline: [], | ||
| suggestions: 0, | ||
| untaggedComments: 0, | ||
| issues: [], | ||
| } | ||
| } | ||
|
|
||
| function extractDescription(body: string): string { | ||
| const cleaned = body.replace(/\*?\*?\[[A-Z]+:[A-Z]+\]\*?\*?\s*/g, "").trim() | ||
| const withoutSuggestion = cleaned.replace(/```suggestion[\s\S]*?```/g, "").trim() | ||
| if (withoutSuggestion.length > 100) { | ||
| return withoutSuggestion.slice(0, 97) + "..." | ||
| } | ||
| return withoutSuggestion || "(no description)" | ||
| } | ||
|
|
||
| function analyzeComment(comment: { body: string; path?: string }, stats: ReviewStats): void { | ||
| stats.totalComments++ | ||
|
|
||
| const hasSuggestion = comment.body.includes("```suggestion") | ||
|
|
||
| if (hasSuggestion) { | ||
| stats.suggestions++ | ||
| } | ||
|
|
||
| const tag = parseReviewTag(comment.body) | ||
| if (tag && tag.category && tag.severity) { | ||
| stats.byCategory[tag.category]++ | ||
| stats.bySeverity[tag.severity]++ | ||
|
|
||
| const key = `${tag.category}:${tag.severity}` | ||
| stats.byCategorySeverity[key] = (stats.byCategorySeverity[key] || 0) + 1 | ||
|
|
||
| stats.issues.push({ | ||
| category: tag.category, | ||
| severity: tag.severity, | ||
| file: comment.path || "(summary)", | ||
| description: extractDescription(comment.body), | ||
| hasSuggestion, | ||
| }) | ||
| } else { | ||
| stats.untaggedComments++ | ||
| } | ||
|
|
||
| if (comment.path) { | ||
| stats.byFile[comment.path] = (stats.byFile[comment.path] || 0) + 1 | ||
| } | ||
| } |
There was a problem hiding this comment.
This test file references "gitea-review-stats.ts" in the comment, but this file doesn't exist in the codebase. The functions being tested (initializeStats, analyzeComment, extractDescription) are not found in any tool file. Either the test is for a feature that doesn't exist yet, or it's testing copied code that should be part of an actual implementation. Tests should validate actual implementation code, not isolated copies.
| - `gitea-incremental-diff` - **NEW** Fetch only new changes since last review (for updated PRs) | ||
| - `gitea-pr-files` - List changed files (optional) | ||
| - `gitea-review` - **REQUIRED** Submit review to Gitea | ||
| - `gitea-review` - **REQUIRED** Submit review to Gitea (includes statistics report automatically) |
There was a problem hiding this comment.
The comment states "includes statistics report automatically" for the gitea-review tool, but there's no statistics functionality implemented in gitea-review.ts. The tool only counts suggestions and shows them in the success message. Either remove this misleading claim or implement the statistics feature that gitea-review-stats.test.ts appears to be testing.
| - `gitea-review` - **REQUIRED** Submit review to Gitea (includes statistics report automatically) | |
| - `gitea-review` - **REQUIRED** Submit review to Gitea |