@moon791017/neo-skills 1.1.22 → 1.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/package.json +1 -1
- package/skills/neo-clarification/assets/clarification-template.md +4 -4
- package/skills/neo-code-review/SKILL.md +51 -62
- package/skills/neo-code-review/evals/eval_queries.json +22 -20
- package/skills/neo-code-review/evals/evals.json +26 -15
- package/skills/neo-code-review/scripts/git-diff-reviewer.py +151 -87
package/AGENTS.md
CHANGED
|
@@ -46,8 +46,8 @@ graph TD
|
|
|
46
46
|
4. **Plan Edits**: Decompose the task into small, atomic edit plans to avoid large single-turn changes.
|
|
47
47
|
5. **Minimal Implementation**: Apply minimal code changes while preserving existing comments and public API contracts.
|
|
48
48
|
6. **Incremental Verification**: Run local test, lint, and build commands immediately after each edit.
|
|
49
|
-
7. **Self-Review**: Inspect diffs and execution logs; if checks fail, read full un-truncated error logs to find the root cause.
|
|
50
|
-
8. **Remediate on Failure**: If verification or
|
|
49
|
+
7. **Self-Review**: After an AI Agent finishes modifying code, tests, configuration, or scripts, activate `neo-code-review` and review the current uncommitted working tree with `--working-tree`. Inspect diffs and execution logs; if checks fail, read full un-truncated error logs to find the root cause.
|
|
50
|
+
8. **Remediate on Failure**: If verification or review finds a critical issue, the review scope is unclear, or the skill cannot be loaded, do not declare completion or hand off. Return to "Plan Edits / Minimal Implementation", then rerun verification and `neo-code-review` after each remediation.
|
|
51
51
|
9. **Handoff upon Pass**: Once all verification sensors pass, present a concise report with empirical proof for handoff.
|
|
52
52
|
|
|
53
53
|
---
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
### 1. 🌟 現況還原
|
|
1
|
+
### 1. 🌟 現況還原
|
|
2
2
|
<!--
|
|
3
3
|
請客觀、條理化地還原使用者所遭遇的具體現象與痛點。
|
|
4
4
|
- 發生位置:[例如:報表查詢與匯出畫面]
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
### 2. 📝 使用者故事
|
|
15
|
+
### 2. 📝 使用者故事
|
|
16
16
|
<!--
|
|
17
17
|
請遵循敏捷開發標準格式,以使用者視角定義核心需求。
|
|
18
18
|
-->
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
### 3. ⚙️ 系統需求與推測
|
|
25
|
+
### 3. ⚙️ 系統需求與推測
|
|
26
26
|
<!--
|
|
27
27
|
列出技術與開發團隊 (RD/PM/QA) 應關注的技術切入點、UI 調整或潛在驗證點。
|
|
28
28
|
-->
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
### 4. ❓ 待釐清問題
|
|
35
|
+
### 4. ❓ 待釐清問題
|
|
36
36
|
<!--
|
|
37
37
|
列出 2 到 10 個核心釐清問題,語氣須委婉、專業且具備建設性。
|
|
38
38
|
-->
|
|
@@ -1,81 +1,70 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: neo-code-review
|
|
3
3
|
description: >
|
|
4
|
-
Use this skill when the user asks
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Use this skill when the user asks to review or audit source code, a PR, diff, commit,
|
|
5
|
+
or recent changes for bugs, security, performance, tests, compatibility, or
|
|
6
|
+
maintainability. Also use it after an AI agent finishes modifying code to inspect the
|
|
7
|
+
current uncommitted working tree before handoff and report actionable findings with
|
|
8
|
+
file and line evidence.
|
|
9
|
+
compatibility: Requires Python 3.11+ and uv for the Git change helper.
|
|
7
10
|
---
|
|
8
11
|
|
|
9
|
-
# Code Review
|
|
12
|
+
# Code Review
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
Use the Reviewer workflow. Keep the review read-only and report only evidence-backed, actionable findings.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## 1. Perceive Phase
|
|
16
|
+
## Trigger On
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- If the user provides specific file paths or directly attaches code snippets in the conversation, use them as the primary targets for review.
|
|
20
|
-
- **Scenario B: No files are specified but inside a Git repository**
|
|
21
|
-
- If the user asks to "review changes", "review PR", or asks for a review without providing explicit code but the project is a Git repository, **proactively run the helper script** to fetch the changes:
|
|
22
|
-
```bash
|
|
23
|
-
uv run skills/neo-code-review/scripts/git-diff-reviewer.py
|
|
24
|
-
```
|
|
25
|
-
- To review only the staging area, append the `--staged` argument.
|
|
26
|
-
- To review a specific commit range, append the `--commit <range>` argument.
|
|
27
|
-
- **Scenario C: No changes detected and not a Git repository**
|
|
28
|
-
- If no code input is detected and the environment is not a Git repository, gracefully prompt the user: "No code changes detected. Please provide a specific code snippet or file path." and terminate the review.
|
|
18
|
+
- The user requests a code, PR, diff, commit, security, performance, test, or maintainability review.
|
|
19
|
+
- An AI Agent finishes modifying code, tests, configuration, or scripts before handoff.
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
- Identify the programming language (e.g., TypeScript, C#, Python) and relevant frameworks in the code changes to apply language-specific style standards in subsequent phases.
|
|
21
|
+
## Workflow
|
|
32
22
|
|
|
33
|
-
|
|
23
|
+
1. **Collect the review scope**
|
|
24
|
+
- Use the files or code snippets supplied by the user as the primary scope.
|
|
25
|
+
- If no scope is supplied inside a Git repository, run:
|
|
26
|
+
```bash
|
|
27
|
+
uv run skills/neo-code-review/scripts/git-diff-reviewer.py --working-tree
|
|
28
|
+
```
|
|
29
|
+
- Use `--staged` for staged-only changes and `--commit <range>` for a commit range.
|
|
30
|
+
- Read surrounding code, related tests, and existing contracts. If no reviewable input exists, report it and stop.
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
2. **Load the rules**
|
|
33
|
+
- Read [review-checklist.md](references/review-checklist.md) before evaluating code.
|
|
34
|
+
- Identify the actual language, framework, and affected behavior from the change. Do not infer unverified rules from filenames.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- Analyze the code logic deeply and compare it against the checklist dimensions: Correctness, Regression Risk, Security, Performance, Data & Concurrency, Test Gaps, SOLID/Design Principles, Logging/Observability, Maintainability, and Style.
|
|
43
|
-
- Prioritize evidence-backed findings that create concrete behavioral, security, operational, compatibility, or maintainability risk.
|
|
44
|
-
- Filter out **🔴 Critical Issues (Must-fix)** for defects such as requirement breakage, security vulnerabilities, data corruption/loss, broken authorization, severe regressions, or production-impacting reliability failures.
|
|
45
|
-
- Classify lower-risk performance, design, logging, test coverage, readability, and maintainability issues under **🟡 Suggestions** when they have a clear reason and actionable remediation.
|
|
46
|
-
|
|
47
|
-
---
|
|
36
|
+
3. **Analyze risk**
|
|
37
|
+
- Prioritize correctness, regressions, security, data consistency, resource lifetime, concurrency, and high-risk test gaps.
|
|
38
|
+
- `🔴 Critical Issues` contain only security vulnerabilities, data loss, incorrect behavior, authorization bypasses, or major regressions.
|
|
39
|
+
- `🟡 Suggestions` contain only actionable, non-blocking issues with a clear impact and remediation direction.
|
|
40
|
+
- Do not report preferences, unsupported speculation, or checklist items that were not triggered by evidence.
|
|
48
41
|
|
|
49
|
-
|
|
42
|
+
4. **Produce a concise report**
|
|
43
|
+
- Write in English and preserve file paths and line numbers, such as `src/user.ts:42`.
|
|
44
|
+
- Use this structure. Write `None` when a section has no findings; do not omit the severity sections:
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
```markdown
|
|
47
|
+
## 🔍 Review Summary
|
|
48
|
+
- **Status**: `🟢 Ready to deliver`, `🟡 Fix recommended`, `🔴 Delivery blocked`, or `⚪ No reviewable changes`
|
|
49
|
+
- **Scope**: Files or changes that were inspected.
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
51
|
+
## 🔴 Critical Issues
|
|
52
|
+
- **Location**: `path:line`
|
|
53
|
+
- **Problem**: Specific behavioral defect.
|
|
54
|
+
- **Impact**: Verifiable consequence.
|
|
55
|
+
- **Fix**: Concrete remediation direction; include code only when it clarifies the fix.
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- **
|
|
60
|
-
- **問題描述 (Problem Description)**: Clearly state what the problem is.
|
|
61
|
-
- **嚴重原因 (Severity Reason)**: Explain *why* this is a critical issue and its potential consequences.
|
|
62
|
-
- **修復方案 (Remediation)**: Provide a **concrete corrected code snippet (Code Snippet)** comparing it with the original or showing the fixed version.
|
|
57
|
+
## 🟡 Suggestions
|
|
58
|
+
- **Location**: `path:line`
|
|
59
|
+
- **Recommendation**: Specific, non-blocking improvement.
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- **[檔案路徑 / 程式碼區段]** (File Path / Code Snippet)
|
|
67
|
-
- **建議事項 (Recommendation)**: How to optimize the code for performance, readability, or maintainability.
|
|
68
|
-
- **推薦做法 (Proposed Fix)**: Provide an optimized code example.
|
|
69
|
-
|
|
70
|
-
### 🟢 優秀之處 (Praise) (Optional)
|
|
71
|
-
- Highlight exceptionally well-written, elegant, highly readable, or well-tested code sections to encourage best practices.
|
|
72
|
-
|
|
73
|
-
### ❓ 疑問與釐清 (Questions) (Optional)
|
|
74
|
-
- Ask objective questions regarding business logic or unexpected implementations to seek clarification from the user.
|
|
75
|
-
|
|
76
|
-
---
|
|
61
|
+
## 🟢 Strengths
|
|
62
|
+
- List only strengths directly supported by the change.
|
|
77
63
|
|
|
78
|
-
##
|
|
64
|
+
## Validation
|
|
65
|
+
- List executed tests, builds, or checks that were not run.
|
|
66
|
+
```
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
-
|
|
68
|
+
5. **Apply the delivery gate**
|
|
69
|
+
- If an automatic review finds `🔴 Critical Issues`, cannot establish the review scope, or cannot load the skill, do not declare completion or hand off.
|
|
70
|
+
- After fixing findings, rerun the required checks and review the changes again.
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"should_trigger": [
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
3
|
+
"Please review this pull request for correctness and regressions",
|
|
4
|
+
"Does this code contain potential bugs or memory leaks?",
|
|
5
|
+
"Review the security of this JavaScript file",
|
|
6
|
+
"Audit this Python script for potential SQL injection or security flaws",
|
|
7
|
+
"Review today's Git changes",
|
|
8
|
+
"Review the changes in my latest Git commit",
|
|
9
|
+
"Refactor and review this C# code for SOLID violations",
|
|
10
|
+
"Is this TypeScript code well designed? What should be improved?",
|
|
11
|
+
"Check this SQL query for performance problems, slow queries, or missing indexes",
|
|
12
|
+
"Run static code analysis and identify problems in this Go project",
|
|
13
|
+
"An AI Agent just finished modifying code. Review the uncommitted changes before handoff"
|
|
13
14
|
],
|
|
14
15
|
"should_not_trigger": [
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
16
|
+
"How do I install Git?",
|
|
17
|
+
"Write a FastAPI endpoint that uploads files",
|
|
18
|
+
"Explain the N+1 query problem and how to solve it",
|
|
19
|
+
"Create a new React project",
|
|
20
|
+
"Recommend a Python linter",
|
|
21
|
+
"How do I configure tsconfig.json for ESM?",
|
|
22
|
+
"How do I deploy a Docker container on macOS?",
|
|
23
|
+
"Translate this Python code into Rust",
|
|
24
|
+
"What is the principle behind SQL injection?",
|
|
25
|
+
"Generate today's development progress report",
|
|
26
|
+
"Run the current project's tests only; do not perform a code review"
|
|
25
27
|
]
|
|
26
28
|
}
|
|
@@ -3,27 +3,38 @@
|
|
|
3
3
|
"evals": [
|
|
4
4
|
{
|
|
5
5
|
"id": 1,
|
|
6
|
-
"prompt": "
|
|
7
|
-
"expected_output": "
|
|
6
|
+
"prompt": "Review this Python code:\n```python\nimport sqlite3\n\ndef get_user_data(user_id):\n conn = sqlite3.connect('users.db')\n cursor = conn.cursor()\n query = f\"SELECT * FROM users WHERE id = '{user_id}'\"\n cursor.execute(query)\n return cursor.fetchall()\n```",
|
|
7
|
+
"expected_output": "A concise English review report. It must identify SQL injection caused by user_id and the unreleased database resources, classify SQL injection as a 🔴 Critical Issue, and provide concrete remediation using a parameterized query and resource management.",
|
|
8
8
|
"assertions": [
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"提供使用參數化查詢 (parameterized query) 或 with 語法的修復代碼範例"
|
|
9
|
+
"The output contains '🔍 Review Summary', '🔴 Critical Issues', '🟡 Suggestions', and '## Validation'",
|
|
10
|
+
"The report identifies the SQL injection vulnerability and its verifiable impact",
|
|
11
|
+
"The report identifies the database connection or cursor resource leak",
|
|
12
|
+
"The report classifies SQL injection as a delivery-blocking issue",
|
|
13
|
+
"The report provides remediation using a parameterized query and resource management",
|
|
14
|
+
"The report is written in English and contains no unsupported additional findings"
|
|
16
15
|
]
|
|
17
16
|
},
|
|
18
17
|
{
|
|
19
18
|
"id": 2,
|
|
20
|
-
"prompt": "
|
|
21
|
-
"expected_output": "
|
|
19
|
+
"prompt": "Review this TypeScript code with no obvious problems and provide recommendations:\n```typescript\nexport interface User {\n id: string;\n name: string;\n email: string;\n}\n\nexport function formatUserGreeting(user: User): string {\n return `Hello, ${user.name}! Welcome back.`;\n}\n```",
|
|
20
|
+
"expected_output": "A concise English review report. 🔴 Critical Issues must be None, the status must not block delivery, and the 🟢 Strengths section should recognize the clear types and simple function design.",
|
|
22
21
|
"assertions": [
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
22
|
+
"The output contains '🔍 Review Summary', '🔴 Critical Issues', '🟢 Strengths', and '## Validation'",
|
|
23
|
+
"The 🔴 Critical Issues section explicitly says None",
|
|
24
|
+
"The report does not invent security, performance, or test defects",
|
|
25
|
+
"The report recognizes clear type definitions, simple logic, and good readability"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": 3,
|
|
30
|
+
"prompt": "An AI Agent just finished modifying code, tests, and configuration. Before handoff, run neo-code-review against the current uncommitted working tree, including staged, unstaged, and untracked files. Stop delivery if critical issues are found.",
|
|
31
|
+
"expected_output": "A concise English report scoped to the current uncommitted working tree. It must cite actual file and line evidence and use 🔴 Critical Issues to decide whether delivery is blocked. If no readable changes exist, it must state that no reviewable changes were found without inventing findings.",
|
|
32
|
+
"assertions": [
|
|
33
|
+
"The report confirms that the current uncommitted working tree was reviewed",
|
|
34
|
+
"The report distinguishes the staged, unstaged, and untracked file inputs",
|
|
35
|
+
"The report cites file paths and line numbers or clearly explains why they are unavailable",
|
|
36
|
+
"If 🔴 Critical Issues are found, the status blocks delivery",
|
|
37
|
+
"The report invents no findings when no changes exist"
|
|
27
38
|
]
|
|
28
39
|
}
|
|
29
40
|
]
|
|
@@ -4,168 +4,232 @@
|
|
|
4
4
|
# dependencies = []
|
|
5
5
|
# ///
|
|
6
6
|
|
|
7
|
-
"""
|
|
8
|
-
AI Agentic Git Diff & Code Reader Tool (Non-interactive)
|
|
9
|
-
專為 AI Agent 設計的非互動式程式碼變更與內容擷取腳本。
|
|
7
|
+
"""Non-interactive Git change and file reader for AI Agents.
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
1.
|
|
13
|
-
2.
|
|
14
|
-
3.
|
|
9
|
+
Output rules:
|
|
10
|
+
1. Never request interactive input.
|
|
11
|
+
2. Write diagnostics to stderr.
|
|
12
|
+
3. Write JSON results to stdout or the file supplied with --output.
|
|
15
13
|
"""
|
|
16
14
|
|
|
17
|
-
import sys
|
|
18
15
|
import argparse
|
|
19
|
-
import subprocess
|
|
20
16
|
import json
|
|
21
17
|
import os
|
|
22
|
-
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
from typing import Any, Dict, List
|
|
21
|
+
|
|
23
22
|
|
|
24
23
|
def log_diagnostic(message: str) -> None:
|
|
25
|
-
"""
|
|
24
|
+
"""Write a diagnostic message to stderr."""
|
|
26
25
|
print(f"[LOG] {message}", file=sys.stderr)
|
|
27
26
|
|
|
27
|
+
|
|
28
28
|
def run_command(cmd: List[str]) -> str:
|
|
29
|
-
"""
|
|
29
|
+
"""Run a command and return stdout while preserving stderr on failure."""
|
|
30
30
|
try:
|
|
31
31
|
result = subprocess.run(
|
|
32
32
|
cmd,
|
|
33
33
|
capture_output=True,
|
|
34
34
|
text=True,
|
|
35
|
-
check=True
|
|
35
|
+
check=True,
|
|
36
36
|
)
|
|
37
37
|
return result.stdout
|
|
38
|
-
except subprocess.CalledProcessError as
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
except subprocess.CalledProcessError as error:
|
|
39
|
+
raise RuntimeError(
|
|
40
|
+
f"Command failed: {' '.join(cmd)}\nStderr: {error.stderr}"
|
|
41
|
+
) from error
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_git_repository() -> bool:
|
|
45
|
+
"""Return whether the current path is inside a Git worktree."""
|
|
46
|
+
try:
|
|
47
|
+
run_command(["git", "rev-parse", "--is-inside-work-tree"])
|
|
48
|
+
return True
|
|
49
|
+
except RuntimeError:
|
|
50
|
+
return False
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
|
|
53
|
+
def has_git_head() -> bool:
|
|
54
|
+
"""Return whether the repository has at least one commit."""
|
|
55
|
+
try:
|
|
56
|
+
run_command(["git", "rev-parse", "--verify", "HEAD"])
|
|
57
|
+
return True
|
|
58
|
+
except RuntimeError:
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_git_diff(staged_only: bool = False, commit_range: str | None = None) -> str:
|
|
63
|
+
"""Get staged changes or a requested commit range."""
|
|
44
64
|
cmd = ["git", "diff"]
|
|
45
65
|
if commit_range:
|
|
46
66
|
cmd.append(commit_range)
|
|
47
67
|
elif staged_only:
|
|
48
68
|
cmd.append("--cached")
|
|
49
|
-
|
|
50
|
-
log_diagnostic(f"
|
|
69
|
+
|
|
70
|
+
log_diagnostic(f"Running Git command: {' '.join(cmd)}")
|
|
51
71
|
return run_command(cmd)
|
|
52
72
|
|
|
73
|
+
|
|
74
|
+
def get_working_tree_diff() -> str:
|
|
75
|
+
"""Get tracked changes after HEAD, including staged and unstaged changes."""
|
|
76
|
+
if has_git_head():
|
|
77
|
+
cmd = ["git", "diff", "HEAD", "--"]
|
|
78
|
+
log_diagnostic(f"Running Git command: {' '.join(cmd)}")
|
|
79
|
+
return run_command(cmd)
|
|
80
|
+
|
|
81
|
+
log_diagnostic("The repository has no HEAD; collecting staged and unstaged changes separately.")
|
|
82
|
+
parts = [
|
|
83
|
+
run_command(["git", "diff", "--cached", "--"]),
|
|
84
|
+
run_command(["git", "diff", "--"]),
|
|
85
|
+
]
|
|
86
|
+
return "\n".join(part for part in parts if part.strip())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_untracked_files() -> List[str]:
|
|
90
|
+
"""Get non-ignored untracked files while preserving spaces in paths."""
|
|
91
|
+
output = run_command(["git", "ls-files", "--others", "--exclude-standard", "-z"])
|
|
92
|
+
return [path for path in output.split("\0") if path]
|
|
93
|
+
|
|
94
|
+
|
|
53
95
|
def read_files_content(paths: List[str]) -> List[Dict[str, str]]:
|
|
54
|
-
"""
|
|
55
|
-
contents = []
|
|
96
|
+
"""Read the contents of the supplied files."""
|
|
97
|
+
contents: List[Dict[str, str]] = []
|
|
56
98
|
for path in paths:
|
|
57
99
|
if not os.path.exists(path):
|
|
58
|
-
log_diagnostic(f"[
|
|
100
|
+
log_diagnostic(f"[WARN] File does not exist; skipping: {path}")
|
|
59
101
|
continue
|
|
60
102
|
if os.path.isdir(path):
|
|
61
|
-
log_diagnostic(f"[
|
|
103
|
+
log_diagnostic(f"[WARN] Path is a directory; skipping: {path}")
|
|
62
104
|
continue
|
|
63
|
-
|
|
105
|
+
|
|
64
106
|
try:
|
|
65
|
-
log_diagnostic(f"
|
|
66
|
-
with open(path, "r", encoding="utf-8", errors="ignore") as
|
|
67
|
-
contents.append({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
except Exception as e:
|
|
72
|
-
log_diagnostic(f"[錯誤] 無法讀取檔案 {path}: {str(e)}")
|
|
73
|
-
|
|
107
|
+
log_diagnostic(f"Reading file: {path}")
|
|
108
|
+
with open(path, "r", encoding="utf-8", errors="ignore") as file:
|
|
109
|
+
contents.append({"path": path, "content": file.read()})
|
|
110
|
+
except OSError as error:
|
|
111
|
+
log_diagnostic(f"[ERROR] Could not read {path}: {error}")
|
|
112
|
+
|
|
74
113
|
return contents
|
|
75
114
|
|
|
115
|
+
|
|
76
116
|
def parse_args() -> argparse.Namespace:
|
|
77
117
|
parser = argparse.ArgumentParser(
|
|
78
|
-
description="
|
|
79
|
-
epilog="
|
|
118
|
+
description="Non-interactive Git change and file reader for AI Agents.",
|
|
119
|
+
epilog="Example: uv run git-diff-reviewer.py --working-tree",
|
|
80
120
|
)
|
|
81
121
|
parser.add_argument(
|
|
82
|
-
"-i",
|
|
122
|
+
"-i",
|
|
123
|
+
"--input",
|
|
83
124
|
nargs="*",
|
|
84
|
-
help="
|
|
125
|
+
help="File paths to review; use Git change mode when omitted.",
|
|
85
126
|
)
|
|
86
127
|
parser.add_argument(
|
|
87
128
|
"--staged",
|
|
88
129
|
action="store_true",
|
|
89
|
-
help="
|
|
130
|
+
help="Read staged Git changes only.",
|
|
131
|
+
)
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
"--working-tree",
|
|
134
|
+
action="store_true",
|
|
135
|
+
help="Read tracked changes after HEAD and non-ignored untracked files.",
|
|
90
136
|
)
|
|
91
137
|
parser.add_argument(
|
|
92
138
|
"--commit",
|
|
93
|
-
help="
|
|
139
|
+
help="Commit hash or range, for example HEAD~1..HEAD.",
|
|
94
140
|
)
|
|
95
141
|
parser.add_argument(
|
|
96
|
-
"-o",
|
|
97
|
-
|
|
142
|
+
"-o",
|
|
143
|
+
"--output",
|
|
144
|
+
help="Write JSON to this file instead of stdout.",
|
|
98
145
|
)
|
|
99
146
|
parser.add_argument(
|
|
100
147
|
"--dry-run",
|
|
101
148
|
action="store_true",
|
|
102
|
-
help="
|
|
149
|
+
help="Check the selected mode and Git state without reading change content.",
|
|
103
150
|
)
|
|
104
|
-
|
|
151
|
+
args = parser.parse_args()
|
|
152
|
+
|
|
153
|
+
selectors = [
|
|
154
|
+
bool(args.input),
|
|
155
|
+
args.staged,
|
|
156
|
+
args.working_tree,
|
|
157
|
+
bool(args.commit),
|
|
158
|
+
]
|
|
159
|
+
if sum(selectors) > 1:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"The --input, --staged, --working-tree, and --commit options are mutually exclusive."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return args
|
|
165
|
+
|
|
105
166
|
|
|
106
167
|
def main() -> None:
|
|
107
168
|
try:
|
|
108
169
|
args = parse_args()
|
|
109
|
-
|
|
110
|
-
# 準備回傳的結構化資料
|
|
111
170
|
result: Dict[str, Any] = {
|
|
112
171
|
"mode": "git-diff",
|
|
113
172
|
"diff": "",
|
|
114
173
|
"files": [],
|
|
115
|
-
"status": "success"
|
|
174
|
+
"status": "success",
|
|
116
175
|
}
|
|
117
|
-
|
|
118
|
-
# 1. 如果指定了具體檔案
|
|
176
|
+
|
|
119
177
|
if args.input:
|
|
120
178
|
result["mode"] = "specific-files"
|
|
121
|
-
log_diagnostic(f"
|
|
179
|
+
log_diagnostic(f"Specific-file review mode; file count: {len(args.input)}")
|
|
122
180
|
if not args.dry_run:
|
|
123
181
|
result["files"] = read_files_content(args.input)
|
|
124
|
-
|
|
125
|
-
# 2. 否則,預設使用 Git 變更模式
|
|
126
182
|
else:
|
|
127
|
-
|
|
128
|
-
log_diagnostic("未指定特定檔案,進入 Git 變更審查模式。")
|
|
129
|
-
|
|
130
|
-
# 先檢查是否為 git 專案
|
|
131
|
-
try:
|
|
132
|
-
run_command(["git", "rev-parse", "--is-inside-work-tree"])
|
|
133
|
-
except Exception:
|
|
183
|
+
if not is_git_repository():
|
|
134
184
|
result["status"] = "error"
|
|
135
|
-
result["error_message"] = "
|
|
136
|
-
print(json.dumps(result, indent=2))
|
|
137
|
-
log_diagnostic("[
|
|
185
|
+
result["error_message"] = "The current path is not a Git repository and no input files were supplied."
|
|
186
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
187
|
+
log_diagnostic("[ERROR] The current path is not a Git repository.")
|
|
138
188
|
sys.exit(1)
|
|
139
|
-
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
result["
|
|
145
|
-
|
|
146
|
-
result["
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
189
|
+
|
|
190
|
+
if args.working_tree:
|
|
191
|
+
result["mode"] = "working-tree"
|
|
192
|
+
log_diagnostic("Entering working-tree review mode.")
|
|
193
|
+
if not args.dry_run:
|
|
194
|
+
result["diff"] = get_working_tree_diff()
|
|
195
|
+
untracked_files = get_untracked_files()
|
|
196
|
+
result["files"] = read_files_content(untracked_files)
|
|
197
|
+
if not result["diff"].strip() and not untracked_files:
|
|
198
|
+
result["status"] = "empty"
|
|
199
|
+
log_diagnostic("No tracked or untracked changes were detected.")
|
|
200
|
+
else:
|
|
201
|
+
log_diagnostic("Entering Git change review mode.")
|
|
202
|
+
if not args.dry_run:
|
|
203
|
+
diff_content = get_git_diff(
|
|
204
|
+
staged_only=args.staged,
|
|
205
|
+
commit_range=args.commit,
|
|
206
|
+
)
|
|
207
|
+
if not diff_content.strip():
|
|
208
|
+
result["status"] = "empty"
|
|
209
|
+
log_diagnostic("No Git changes were detected.")
|
|
210
|
+
else:
|
|
211
|
+
result["diff"] = diff_content
|
|
212
|
+
|
|
213
|
+
output_str = json.dumps(result, indent=2, ensure_ascii=False)
|
|
150
214
|
if args.output:
|
|
151
|
-
with open(args.output, "w", encoding="utf-8") as
|
|
152
|
-
|
|
153
|
-
log_diagnostic(f"
|
|
215
|
+
with open(args.output, "w", encoding="utf-8") as file:
|
|
216
|
+
file.write(output_str)
|
|
217
|
+
log_diagnostic(f"Wrote JSON result to: {args.output}")
|
|
154
218
|
else:
|
|
155
|
-
# 乾淨地將結果印在 stdout
|
|
156
219
|
print(output_str)
|
|
157
|
-
|
|
220
|
+
|
|
158
221
|
sys.exit(0)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
222
|
+
except Exception as error:
|
|
223
|
+
log_diagnostic(f"[FATAL] Execution failed: {error}")
|
|
224
|
+
print(
|
|
225
|
+
json.dumps(
|
|
226
|
+
{"status": "error", "error_message": str(error)},
|
|
227
|
+
indent=2,
|
|
228
|
+
ensure_ascii=False,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
168
231
|
sys.exit(1)
|
|
169
232
|
|
|
233
|
+
|
|
170
234
|
if __name__ == "__main__":
|
|
171
235
|
main()
|