forked from Gitlink/gitlink-cli
Merge branch 'zk_branch' of https://www.gitlink.org.cn/zzx-coder/gitlink-cli into zk_branch
# Conflicts: # skills/README.md
This commit is contained in:
commit
a7513cf683
|
|
@ -48,14 +48,12 @@ func New() (*Client, error) {
|
|||
func (c *Client) Do(method, path string, body interface{}, query url.Values) (*output.Envelope, error) {
|
||||
// Append .json suffix if not already present (GitLink API convention)
|
||||
// Handle paths that may already contain query strings (e.g., /path?key=val)
|
||||
if !c.SkipJSONSuffix {
|
||||
if c.shouldAppendJSONSuffix(path) {
|
||||
if idx := strings.Index(path, "?"); idx != -1 {
|
||||
basePath := path[:idx]
|
||||
queryStr := path[idx:]
|
||||
if !strings.HasSuffix(basePath, ".json") {
|
||||
path = basePath + ".json" + queryStr
|
||||
}
|
||||
} else if !strings.HasSuffix(path, ".json") {
|
||||
path = basePath + ".json" + queryStr
|
||||
} else {
|
||||
path += ".json"
|
||||
}
|
||||
}
|
||||
|
|
@ -225,3 +223,24 @@ func lookupStatusInfo(code int) statusInfo {
|
|||
message: fmt.Sprintf("API 返回错误码 %d", code),
|
||||
}
|
||||
}
|
||||
|
||||
// shouldAppendJSONSuffix reports whether the .json suffix should be appended to path.
|
||||
// Returns false (skip append) when:
|
||||
// - c.SkipJSONSuffix is set (explicit opt-out for non-JSON endpoints such as gateway)
|
||||
// - path already ends with .json
|
||||
// - path matches the raw content pattern (e.g., /api/:owner/:repo/raw/...)
|
||||
func (c *Client) shouldAppendJSONSuffix(path string) bool {
|
||||
if c.SkipJSONSuffix {
|
||||
return false
|
||||
}
|
||||
if strings.HasSuffix(path, ".json") {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
for i, part := range parts {
|
||||
if part == "raw" && i >= 2 && i+2 < len(parts) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,21 +8,27 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
DefaultBaseURL = "https://www.gitlink.org.cn/api"
|
||||
DefaultFormat = "table"
|
||||
DefaultBaseURL = "https://www.gitlink.org.cn/api"
|
||||
DefaultGatewayBaseURL = "https://gateway.gitlink.org.cn/api"
|
||||
DefaultFormat = "table"
|
||||
|
||||
// EnvGatewayBaseURL overrides GatewayBaseURL when set.
|
||||
EnvGatewayBaseURL = "GITLINK_GATEWAY_URL"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaseURL string `yaml:"base_url"`
|
||||
Format string `yaml:"default_format"`
|
||||
Editor string `yaml:"editor,omitempty"`
|
||||
Pager string `yaml:"pager,omitempty"`
|
||||
BaseURL string `yaml:"base_url"`
|
||||
GatewayBaseURL string `yaml:"gateway_base_url,omitempty"`
|
||||
Format string `yaml:"default_format"`
|
||||
Editor string `yaml:"editor,omitempty"`
|
||||
Pager string `yaml:"pager,omitempty"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: DefaultBaseURL,
|
||||
Format: DefaultFormat,
|
||||
BaseURL: DefaultBaseURL,
|
||||
GatewayBaseURL: DefaultGatewayBaseURL,
|
||||
Format: DefaultFormat,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +59,12 @@ func Load() (*Config, error) {
|
|||
if cfg.BaseURL == "" {
|
||||
cfg.BaseURL = DefaultBaseURL
|
||||
}
|
||||
if cfg.GatewayBaseURL == "" {
|
||||
cfg.GatewayBaseURL = DefaultGatewayBaseURL
|
||||
}
|
||||
if v := os.Getenv(EnvGatewayBaseURL); v != "" {
|
||||
cfg.GatewayBaseURL = v
|
||||
}
|
||||
if cfg.Format == "" {
|
||||
cfg.Format = DefaultFormat
|
||||
}
|
||||
|
|
@ -79,6 +91,8 @@ func Get(key string) (string, error) {
|
|||
switch key {
|
||||
case "base_url":
|
||||
return cfg.BaseURL, nil
|
||||
case "gateway_base_url":
|
||||
return cfg.GatewayBaseURL, nil
|
||||
case "default_format":
|
||||
return cfg.Format, nil
|
||||
case "editor":
|
||||
|
|
@ -98,6 +112,8 @@ func Set(key, value string) error {
|
|||
switch key {
|
||||
case "base_url":
|
||||
cfg.BaseURL = value
|
||||
case "gateway_base_url":
|
||||
cfg.GatewayBaseURL = value
|
||||
case "default_format":
|
||||
cfg.Format = value
|
||||
case "editor":
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
PR Test 2026年 4月 7日 星期二 11时45分56秒 CST
|
||||
|
|
@ -4,12 +4,14 @@ import (
|
|||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/cmd/cmdutil"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/client"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/config"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/context"
|
||||
clierrors "github.com/gitlink-org/gitlink-cli/internal/errors"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/output"
|
||||
|
|
@ -21,7 +23,7 @@ type Shortcut struct {
|
|||
Description string
|
||||
Flags []Flag
|
||||
DryRun bool // 是否支持 dry-run
|
||||
DryRunHint func(ctx *RuntimeContext) (string, error) // 返回预览描述
|
||||
DryRunHint func(ctx *RuntimeContext) (string, error) // 返回预览描述
|
||||
Run func(ctx *RuntimeContext) error
|
||||
}
|
||||
|
||||
|
|
@ -37,12 +39,14 @@ type Flag struct {
|
|||
|
||||
// RuntimeContext provides helpers for shortcut implementations.
|
||||
type RuntimeContext struct {
|
||||
Client *client.Client
|
||||
Owner string
|
||||
Repo string
|
||||
Format string
|
||||
CommandName string
|
||||
Args map[string]string
|
||||
Client *client.Client
|
||||
Owner string
|
||||
Repo string
|
||||
Format string
|
||||
CommandName string
|
||||
Args map[string]string
|
||||
GatewayBaseURL string
|
||||
GatewayHTTPClient *http.Client // optional; nil = use auth.NewHTTPClient (mainly for tests)
|
||||
}
|
||||
|
||||
// NewRuntimeContext creates a RuntimeContext with auto-resolved owner/repo.
|
||||
|
|
@ -58,13 +62,20 @@ func NewRuntimeContext(args map[string]string, commandName string) (*RuntimeCont
|
|||
format = "json"
|
||||
}
|
||||
|
||||
gatewayBaseURL := config.DefaultGatewayBaseURL
|
||||
if cfg, err := config.Load(); err == nil && cfg.GatewayBaseURL != "" {
|
||||
gatewayBaseURL = cfg.GatewayBaseURL
|
||||
}
|
||||
|
||||
return &RuntimeContext{
|
||||
Client: cli,
|
||||
Owner: cmdutil.Owner,
|
||||
Repo: cmdutil.Repo,
|
||||
Format: format,
|
||||
CommandName: commandName,
|
||||
Args: args,
|
||||
Client: cli,
|
||||
Owner: cmdutil.Owner,
|
||||
Repo: cmdutil.Repo,
|
||||
Format: format,
|
||||
CommandName: commandName,
|
||||
Args: args,
|
||||
GatewayBaseURL: gatewayBaseURL,
|
||||
GatewayHTTPClient: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## 2026-05-31 新增 `wiki +lint` 文档质量检查命令
|
||||
|
||||
> **注意:`+lint` 命令当前仅在本地编译版本中可用**(全局安装的 `gitlink-cli` 暂未包含)。需先 `go build -o gitlink-cli.exe .` 然后使用 `./gitlink-cli.exe wiki +lint`。
|
||||
|
||||
### 使用方法
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -18,32 +18,35 @@ import (
|
|||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
const gatewayBaseURL = "https://gateway.gitlink.org.cn/api"
|
||||
|
||||
var (
|
||||
projectIDCache sync.Map
|
||||
gatewayClient *client.Client
|
||||
gatewayOnce sync.Once
|
||||
)
|
||||
var projectIDCache sync.Map
|
||||
|
||||
func wikiPath(endpoint string) string {
|
||||
return "/wiki/open/" + endpoint
|
||||
}
|
||||
|
||||
func getGatewayClient() *client.Client {
|
||||
gatewayOnce.Do(func() {
|
||||
gatewayClient = &client.Client{
|
||||
HTTP: auth.NewHTTPClient(),
|
||||
BaseURL: gatewayBaseURL,
|
||||
SkipJSONSuffix: true,
|
||||
}
|
||||
})
|
||||
return gatewayClient
|
||||
// getGatewayClient returns a client targeting the Wiki API gateway.
|
||||
// The BaseURL is resolved from RuntimeContext.GatewayBaseURL, which in turn
|
||||
// honours (in order): GITLINK_GATEWAY_URL env > config gateway_base_url > default.
|
||||
// HTTP client falls back to auth.NewHTTPClient() when ctx.GatewayHTTPClient is nil.
|
||||
func getGatewayClient(ctx *common.RuntimeContext) *client.Client {
|
||||
baseURL := ctx.GatewayBaseURL
|
||||
if baseURL == "" {
|
||||
baseURL = "https://gateway.gitlink.org.cn/api"
|
||||
}
|
||||
httpClient := ctx.GatewayHTTPClient
|
||||
if httpClient == nil {
|
||||
httpClient = auth.NewHTTPClient()
|
||||
}
|
||||
return &client.Client{
|
||||
HTTP: httpClient,
|
||||
BaseURL: baseURL,
|
||||
SkipJSONSuffix: true,
|
||||
Debug: ctx.Client.Debug,
|
||||
}
|
||||
}
|
||||
|
||||
func callWikiAPI(ctx *common.RuntimeContext, method, path string, body interface{}) (*output.Envelope, error) {
|
||||
gc := getGatewayClient()
|
||||
gc.Debug = ctx.Client.Debug
|
||||
gc := getGatewayClient(ctx)
|
||||
env, err := gc.Do(method, path, body, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -52,8 +55,7 @@ func callWikiAPI(ctx *common.RuntimeContext, method, path string, body interface
|
|||
}
|
||||
|
||||
func callWikiAPIWithQuery(ctx *common.RuntimeContext, method, path string, query url.Values) (*output.Envelope, error) {
|
||||
gc := getGatewayClient()
|
||||
gc.Debug = ctx.Client.Debug
|
||||
gc := getGatewayClient(ctx)
|
||||
env, err := gc.Do(method, path, nil, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -218,12 +220,12 @@ type LintIssue struct {
|
|||
}
|
||||
|
||||
type LintSummary struct {
|
||||
Repository string `json:"repository"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
TotalIssues int `json:"total_issues"`
|
||||
Errors int `json:"errors"`
|
||||
Warnings int `json:"warnings"`
|
||||
Results []LintIssue `json:"results"`
|
||||
Repository string `json:"repository"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
TotalIssues int `json:"total_issues"`
|
||||
Errors int `json:"errors"`
|
||||
Warnings int `json:"warnings"`
|
||||
Results []LintIssue `json:"results"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
package wiki
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
|
|
@ -234,3 +238,407 @@ func TestResolveProjectID_APIError(t *testing.T) {
|
|||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- callWikiAPI HTTP request path tests ----
|
||||
|
||||
func TestCallWikiAPI_Success(t *testing.T) {
|
||||
resetProjectIDCache()
|
||||
var receivedPath, receivedMethod string
|
||||
var receivedBody []byte
|
||||
server := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedPath = r.URL.Path
|
||||
receivedMethod = r.Method
|
||||
if r.Body != nil {
|
||||
buf := make([]byte, 1024)
|
||||
n, _ := r.Body.Read(buf)
|
||||
receivedBody = buf[:n]
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"code":200,"msg":"ok","data":{"id":42}}`))
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
GatewayBaseURL: server.URL,
|
||||
GatewayHTTPClient: server.Client(),
|
||||
}
|
||||
|
||||
env, err := callWikiAPI(ctx, "POST", "/wiki/open/test", map[string]string{"foo": "bar"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if receivedMethod != "POST" {
|
||||
t.Errorf("method = %q, want POST", receivedMethod)
|
||||
}
|
||||
if receivedPath != "/wiki/open/test" {
|
||||
t.Errorf("path = %q, want /wiki/open/test", receivedPath)
|
||||
}
|
||||
if !strings.Contains(string(receivedBody), `"foo"`) {
|
||||
t.Errorf("body should contain foo: %s", string(receivedBody))
|
||||
}
|
||||
data, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected map data, got %T", env.Data)
|
||||
}
|
||||
if data["id"] != float64(42) {
|
||||
t.Errorf("data[id] = %v, want 42", data["id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWikiAPI_BusinessError(t *testing.T) {
|
||||
resetProjectIDCache()
|
||||
server := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"code":400,"msg":"bad request","data":null}`))
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
GatewayBaseURL: server.URL,
|
||||
GatewayHTTPClient: server.Client(),
|
||||
}
|
||||
_, err := callWikiAPI(ctx, "GET", "/test", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "400") || !strings.Contains(err.Error(), "bad request") {
|
||||
t.Errorf("err = %q, want to contain 400 and bad request", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWikiAPI_GatewayHTTPError(t *testing.T) {
|
||||
resetProjectIDCache()
|
||||
server := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
w.Write([]byte(`upstream error`))
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
GatewayBaseURL: server.URL,
|
||||
GatewayHTTPClient: server.Client(),
|
||||
}
|
||||
_, err := callWikiAPI(ctx, "GET", "/test", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error on 502")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWikiAPI_SkipsJSONSuffix(t *testing.T) {
|
||||
// Verifies the SkipJSONSuffix path is correctly taken for gateway:
|
||||
// the URL should NOT have a .json appended.
|
||||
resetProjectIDCache()
|
||||
var receivedPath string
|
||||
server := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedPath = r.URL.Path
|
||||
w.Write([]byte(`{"code":200,"data":{}}`))
|
||||
})
|
||||
defer server.Close()
|
||||
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: server.Client(), BaseURL: server.URL},
|
||||
GatewayBaseURL: server.URL,
|
||||
GatewayHTTPClient: server.Client(),
|
||||
}
|
||||
_, err := callWikiAPI(ctx, "GET", "/wiki/open/list", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if receivedPath != "/wiki/open/list" {
|
||||
t.Errorf("path = %q, want /wiki/open/list (no .json suffix)", receivedPath)
|
||||
}
|
||||
if strings.HasSuffix(receivedPath, ".json") {
|
||||
t.Errorf("path %q should NOT have .json suffix (gateway expects no suffix)", receivedPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWikiAPI_ConnectionRefused(t *testing.T) {
|
||||
resetProjectIDCache()
|
||||
// Use an unbound port to simulate connection failure
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: &http.Client{}, BaseURL: "http://127.0.0.1:1"},
|
||||
GatewayBaseURL: "http://127.0.0.1:1",
|
||||
GatewayHTTPClient: &http.Client{},
|
||||
}
|
||||
_, err := callWikiAPI(ctx, "GET", "/test", nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected connection error")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- runLint / check rules tests ----
|
||||
|
||||
func TestIsCheckEnabled_Empty(t *testing.T) {
|
||||
if !isCheckEnabled("", "any") {
|
||||
t.Error("empty filter should enable all checks")
|
||||
}
|
||||
if !isCheckEnabled("empty,headings", "empty") {
|
||||
t.Error("should enable 'empty' in filter list")
|
||||
}
|
||||
if isCheckEnabled("headings", "empty") {
|
||||
t.Error("should not enable 'empty' when not in filter list")
|
||||
}
|
||||
if !isCheckEnabled(" empty , headings ", "empty") {
|
||||
t.Error("should trim whitespace")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckEmpty(t *testing.T) {
|
||||
issues := checkEmpty("p1", "")
|
||||
if len(issues) != 1 || issues[0].Level != "error" || issues[0].Check != "empty" {
|
||||
t.Errorf("expected 1 error-level 'empty' issue, got %+v", issues)
|
||||
}
|
||||
if issues := checkEmpty("p1", "some content"); issues != nil {
|
||||
t.Errorf("non-empty content should not produce issues, got %+v", issues)
|
||||
}
|
||||
if issues := checkEmpty("p1", " \n\t "); len(issues) != 1 {
|
||||
t.Errorf("whitespace-only content should be empty, got %+v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckHeading(t *testing.T) {
|
||||
// missing H1
|
||||
if issues := checkHeading("p1", "Some text without heading"); len(issues) != 1 {
|
||||
t.Errorf("expected 1 missing-heading issue, got %+v", issues)
|
||||
}
|
||||
// has H1
|
||||
if issues := checkHeading("p1", "# Title\nbody"); issues != nil {
|
||||
t.Errorf("H1 should not produce issues, got %+v", issues)
|
||||
}
|
||||
// empty content skipped
|
||||
if issues := checkHeading("p1", ""); issues != nil {
|
||||
t.Errorf("empty content should be skipped, got %+v", issues)
|
||||
}
|
||||
// whitespace prefix
|
||||
if issues := checkHeading("p1", " \n# Real Title"); issues != nil {
|
||||
t.Errorf("H1 after whitespace should not produce issues, got %+v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckShort(t *testing.T) {
|
||||
if issues := checkShort("p1", ""); issues != nil {
|
||||
t.Errorf("empty content should be skipped, got %+v", issues)
|
||||
}
|
||||
if issues := checkShort("p1", "short"); len(issues) != 1 {
|
||||
t.Errorf("expected 1 short issue, got %+v", issues)
|
||||
}
|
||||
long := strings.Repeat("a", 100)
|
||||
if issues := checkShort("p1", long); issues != nil {
|
||||
t.Errorf("long content should not produce issues, got %+v", issues)
|
||||
}
|
||||
// exactly 49 chars triggers
|
||||
if issues := checkShort("p1", strings.Repeat("a", 49)); len(issues) != 1 {
|
||||
t.Errorf("49-char content should be 'short', got %+v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckDeadLinks(t *testing.T) {
|
||||
known := map[string]bool{"Home": true, "Guide": true}
|
||||
|
||||
// All known: no issues
|
||||
if issues := checkDeadLinks("p1", "[Home](Home) and [Guide](Guide)", known); issues != nil {
|
||||
t.Errorf("all-known should not produce issues, got %+v", issues)
|
||||
}
|
||||
// Unknown link
|
||||
issues := checkDeadLinks("p1", "[Unknown](Unknown)", known)
|
||||
if len(issues) != 1 || issues[0].Check != "links" {
|
||||
t.Errorf("expected 1 dead link issue, got %+v", issues)
|
||||
}
|
||||
// External links skipped
|
||||
if issues := checkDeadLinks("p1", "[ext](https://example.com)", known); issues != nil {
|
||||
t.Errorf("external links should be skipped, got %+v", issues)
|
||||
}
|
||||
// Anchor links skipped
|
||||
if issues := checkDeadLinks("p1", "[anchor](#section)", known); issues != nil {
|
||||
t.Errorf("anchor links should be skipped, got %+v", issues)
|
||||
}
|
||||
// Mixed
|
||||
issues = checkDeadLinks("p1", "[Home](Home) and [Bad](BadPage)", known)
|
||||
if len(issues) != 1 {
|
||||
t.Errorf("expected 1 dead link in mixed, got %+v", issues)
|
||||
}
|
||||
// Empty content
|
||||
if issues := checkDeadLinks("p1", "", known); issues != nil {
|
||||
t.Errorf("empty content should not produce issues, got %+v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckImages(t *testing.T) {
|
||||
// Mock image server
|
||||
imgServer := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "HEAD" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
defer imgServer.Close()
|
||||
|
||||
brokenServer := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
defer brokenServer.Close()
|
||||
|
||||
httpClient := imgServer.Client()
|
||||
// Valid image (200)
|
||||
if issues := checkImages("p1", "", httpClient); issues != nil {
|
||||
t.Errorf("200 image should not produce issues, got %+v", issues)
|
||||
}
|
||||
// Broken image (404)
|
||||
issues := checkImages("p1", "", httpClient)
|
||||
if len(issues) != 1 {
|
||||
t.Errorf("expected 1 broken image issue, got %+v", issues)
|
||||
}
|
||||
// No images
|
||||
if issues := checkImages("p1", "no images here", httpClient); issues != nil {
|
||||
t.Errorf("no images should not produce issues, got %+v", issues)
|
||||
}
|
||||
// Malformed HTTP URL (regex matches https?:// but http.NewRequest fails to parse)
|
||||
if issues := checkImages("p1", "", httpClient); len(issues) != 1 {
|
||||
t.Errorf("expected 1 invalid-URL issue, got %+v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunLint_Integration(t *testing.T) {
|
||||
resetProjectIDCache()
|
||||
|
||||
// Mock main API (project detail) and gateway (wiki list + get)
|
||||
mainServer := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/detail.json") {
|
||||
writeJSON(t, w, map[string]interface{}{"project_id": float64(999)})
|
||||
return
|
||||
}
|
||||
t.Errorf("unexpected main API call: %s %s", r.Method, r.URL.Path)
|
||||
})
|
||||
defer mainServer.Close()
|
||||
|
||||
// Page content (base64 encoded)
|
||||
goodContent := base64.StdEncoding.EncodeToString([]byte("# Good Page\n\n" + strings.Repeat("This is a well-formed page with enough content to pass the short check. ", 3)))
|
||||
|
||||
wikiServer := newMockServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/wiki/open/wikiPages":
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"code": 200,
|
||||
"data": []map[string]interface{}{
|
||||
{"title": "Good", "sub_url": "Good"},
|
||||
{"title": "Empty", "sub_url": "Empty"},
|
||||
{"title": "_Sidebar", "sub_url": "_Sidebar"}, // system page - skipped
|
||||
},
|
||||
})
|
||||
case "/wiki/open/getWiki":
|
||||
pageName := r.URL.Query().Get("pageName")
|
||||
var content string
|
||||
if pageName == "Empty" {
|
||||
content = "" // empty page
|
||||
} else {
|
||||
content = goodContent
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"code": 200,
|
||||
"data": map[string]interface{}{"content_base64": content},
|
||||
})
|
||||
default:
|
||||
t.Errorf("unexpected wiki path: %s", r.URL.Path)
|
||||
}
|
||||
})
|
||||
defer wikiServer.Close()
|
||||
|
||||
ctx := &common.RuntimeContext{
|
||||
Client: &client.Client{HTTP: mainServer.Client(), BaseURL: mainServer.URL},
|
||||
Owner: "owner1",
|
||||
Repo: "repo1",
|
||||
Format: "json",
|
||||
GatewayBaseURL: wikiServer.URL,
|
||||
GatewayHTTPClient: wikiServer.Client(),
|
||||
}
|
||||
|
||||
if err := runLint(ctx); err != nil {
|
||||
t.Fatalf("runLint: %v", err)
|
||||
}
|
||||
// _Sidebar is skipped, so TotalPages=2 (Good, Empty)
|
||||
// Empty page produces 1 "empty" error
|
||||
// We can't directly inspect the output envelope, but if no error, the function ran end-to-end
|
||||
}
|
||||
|
||||
// ---- resolveContent tests ----
|
||||
|
||||
func TestResolveContent_FromArg(t *testing.T) {
|
||||
ctx := &common.RuntimeContext{
|
||||
Args: map[string]string{"content": "inline content"},
|
||||
}
|
||||
got, err := resolveContent(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "inline content" {
|
||||
t.Errorf("got %q, want %q", got, "inline content")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveContent_FromFile(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := filepath.Join(tmpDir, "wiki.md")
|
||||
want := "# Title\n\nBody content from file"
|
||||
if err := os.WriteFile(path, []byte(want), 0600); err != nil {
|
||||
t.Fatalf("setup: %v", err)
|
||||
}
|
||||
ctx := &common.RuntimeContext{
|
||||
Args: map[string]string{"file": path},
|
||||
}
|
||||
got, err := resolveContent(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveContent_ArgTakesPrecedence(t *testing.T) {
|
||||
// When both --content and --file are set, --content wins
|
||||
tmpDir := t.TempDir()
|
||||
path := filepath.Join(tmpDir, "wiki.md")
|
||||
if err := os.WriteFile(path, []byte("from file"), 0600); err != nil {
|
||||
t.Fatalf("setup: %v", err)
|
||||
}
|
||||
ctx := &common.RuntimeContext{
|
||||
Args: map[string]string{
|
||||
"content": "from arg",
|
||||
"file": path,
|
||||
},
|
||||
}
|
||||
got, err := resolveContent(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "from arg" {
|
||||
t.Errorf("arg should take precedence; got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveContent_Missing(t *testing.T) {
|
||||
ctx := &common.RuntimeContext{
|
||||
Args: map[string]string{},
|
||||
}
|
||||
_, err := resolveContent(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when neither --content nor --file is provided")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "required") {
|
||||
t.Errorf("err = %q, want to mention 'required'", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveContent_FileNotFound(t *testing.T) {
|
||||
ctx := &common.RuntimeContext{
|
||||
Args: map[string]string{"file": "/nonexistent/path/to/wiki.md"},
|
||||
}
|
||||
_, err := resolveContent(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for nonexistent file")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,22 @@ skills/
|
|||
│ ├── REFERENCE.md # Release API 参考
|
||||
│ └── examples/
|
||||
│ └── release-workflow.md # Release 工作流
|
||||
├── gitlink-changelog/ # Release Notes / Changelog 生成
|
||||
│ ├── SKILL.md # Changelog 操作指南
|
||||
│ ├── references/
|
||||
│ │ ├── collect-data.md # 收集变更数据
|
||||
│ │ ├── classify-rules.md # 变更分类规则
|
||||
│ │ └── generate-and-publish.md # 生成并发布
|
||||
│ └── examples/
|
||||
│ └── full-workflow.md # 完整生成示例
|
||||
├── gitlink-health/ # 项目健康度报告
|
||||
│ ├── SKILL.md # 健康度报告操作指南
|
||||
│ ├── references/
|
||||
│ │ ├── collect-data.md # 收集项目数据
|
||||
│ │ ├── health-metrics.md # 指标计算和评分规则
|
||||
│ │ └── generate-report.md # 报告生成和输出
|
||||
│ └── examples/
|
||||
│ └── full-workflow.md # 完整生成示例
|
||||
├── gitlink-search/ # 搜索功能
|
||||
│ ├── SKILL.md # 搜索操作指南
|
||||
│ └── examples/
|
||||
|
|
@ -112,6 +128,11 @@ skills/
|
|||
│ └── SKILL.md # PM 操作指南
|
||||
├── gitlink-workflow/ # AI 自动化工作流
|
||||
│ └── SKILL.md # 工作流模板(Issue 分类、PR Review、Release Notes)
|
||||
├── gitlink-issue-triage/ # Issue 自动分类
|
||||
│ ├── SKILL.md # AI Agent 主入口
|
||||
│ ├── README.md # 使用说明
|
||||
│ ├── references/ # 分析算法 + 应用手册
|
||||
│ └── examples/ # 批量/单 Issue 工作流示例
|
||||
├── gitlink-webhook/ # Webhook 管理
|
||||
│ └── SKILL.md # Webhook 操作指南
|
||||
├── gitlink-compliance/ # 安全与合规
|
||||
|
|
@ -140,6 +161,7 @@ skills/
|
|||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +review` |
|
||||
| **gitlink-branch** | 分支管理 | `branch +list`, `branch +create`, `branch +delete`, `branch +protect` |
|
||||
| **gitlink-release** | 版本发布 | `release +list`, `release +create`, `release +view` |
|
||||
| **gitlink-health** | 项目健康度报告 | Issue 响应时间、PR 合并效率、贡献者活跃度统计 |
|
||||
|
||||
### 辅助 Skills
|
||||
|
||||
|
|
@ -151,7 +173,9 @@ skills/
|
|||
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
|
||||
| **gitlink-wiki** | Wiki 管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` |
|
||||
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
|
||||
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
|
||||
| **gitlink-changelog** | Release Notes / Changelog 生成 | 自动收集 commits/PR/Issue,生成结构化版本说明 |
|
||||
| **gitlink-issue-triage** | Issue 自动分类 | 自动判定 tracker/priority/labels,关联 Issue,生成审计报告 |
|
||||
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、仓库初始化、Sprint 报告 |
|
||||
| **gitlink-webhook** | Webhook 管理 | `webhook +list`, `webhook +create`, `webhook +test` |
|
||||
| **gitlink-compliance** | 安全与合规 | `compliance +scan`, `compliance +secrets`, `compliance +license` |
|
||||
| **gitlink-onboard** | 新人引导 | `onboard +welcome` |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
name: gitlink-changelog
|
||||
version: 1.0.0
|
||||
description: "Release Notes 生成:根据 commit 和 PR 记录自动生成结构化版本发布说明。当用户需要生成 Release Notes、发版说明时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli release --help"
|
||||
---
|
||||
|
||||
# gitlink-changelog(Release Notes 生成)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有 Shortcuts 在执行写入/删除操作前,务必先确认用户意图。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
|
||||
|
||||
## 工作流
|
||||
|
||||
Release Notes 生成分三步:收集 → 分类 → 发布。
|
||||
|
||||
| 步骤 | 说明 | 所用命令 |
|
||||
|------|------|----------|
|
||||
| 1. 收集数据 | 获取版本间的 commits、已合并 PR、已关闭 Issue | `release +list`, `api GET compare`, `pr +list`, `issue +list` |
|
||||
| 2. 分类整理 | 按类型归类变更(新功能/Bug修复/改进/破坏性变更) | AI 分析 |
|
||||
| 3. 生成发布 | 套用模板生成 Notes,创建或更新 Release | `release +create`, `release +update` |
|
||||
|
||||
## 命令参考
|
||||
|
||||
### 收集数据
|
||||
|
||||
```bash
|
||||
# 确定版本范围:获取已有 release 列表,找上一个 tag
|
||||
gitlink-cli release +list --format json
|
||||
|
||||
# 获取两个版本间的 commit 差异(平台 compare API)
|
||||
gitlink-cli api GET /:owner/:repo/compare/v1.0.0...v1.1.0 --format json
|
||||
|
||||
# 获取已合并的 PR
|
||||
gitlink-cli pr +list --state merged --format json
|
||||
|
||||
# 获取已关闭的 Issue
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
```
|
||||
|
||||
### 发布 Release Notes
|
||||
|
||||
```bash
|
||||
# 创建 Release 并附带 Notes
|
||||
gitlink-cli release +create --tag v1.1.0 --name "v1.1.0" --body "<Markdown 格式的 Release Notes>"
|
||||
|
||||
# 更新已有 Release 的 Notes
|
||||
gitlink-cli release +update --id <version_id> --body "<更新后的 Notes>"
|
||||
```
|
||||
|
||||
## 分类规则
|
||||
|
||||
| 类型 | 图标 | Issue 标签 | Commit 关键词 |
|
||||
|------|------|------------|---------------|
|
||||
| 新功能 | ✨ | `feature`, `enhancement` | `feat:`, `add`, `新增` |
|
||||
| Bug 修复 | 🐛 | `bug`, `fix` | `fix:`, `bugfix`, `修复` |
|
||||
| 功能改进 | 🔧 | `improvement`, `optimize` | `improve:`, `optimize:`, `refactor:` |
|
||||
| 破坏性变更 | ⚠️ | `breaking`, `major` | `BREAKING`, `breaking:`, `!` |
|
||||
| 文档 | 📝 | `docs`, `documentation` | `docs:`, `doc` |
|
||||
| 安全修复 | 🔒 | `security`, `vulnerability` | `security:`, `安全` |
|
||||
|
||||
## Release Notes 模板
|
||||
|
||||
### 标准模板
|
||||
|
||||
```markdown
|
||||
# 🎉 Release {VERSION}
|
||||
|
||||
## 📊 变更统计
|
||||
- **新功能**: {FEATURE_COUNT} 个
|
||||
- **Bug 修复**: {BUG_FIX_COUNT} 个
|
||||
- **功能改进**: {ENHANCEMENT_COUNT} 个
|
||||
- **破坏性变更**: {BREAKING_COUNT} 个
|
||||
|
||||
## ✨ 新功能
|
||||
{FEATURES}
|
||||
|
||||
## 🐛 Bug 修复
|
||||
{BUG_FIXES}
|
||||
|
||||
## 🔧 功能改进
|
||||
{ENHANCEMENTS}
|
||||
|
||||
## ⚠️ 破坏性变更
|
||||
{BREAKING_CHANGES}
|
||||
|
||||
## 🙏 贡献者
|
||||
{CONTRIBUTORS}
|
||||
|
||||
---
|
||||
**完整变更日志**: https://www.gitlink.org.cn/{OWNER}/{REPO}/compare/{PREV}...{VERSION}
|
||||
```
|
||||
|
||||
> **条目格式要求**:每个变更条目必须在末尾标注作者,格式为 `- 变更描述 (#编号) (@作者)`。commit 无 PR 编号时格式为 `- 变更描述 (作者名)`。
|
||||
|
||||
### 简化模板
|
||||
|
||||
```markdown
|
||||
# {VERSION}
|
||||
|
||||
## 新增
|
||||
{FEATURES}
|
||||
|
||||
## 修复
|
||||
{BUG_FIXES}
|
||||
|
||||
## 改进
|
||||
{ENHANCEMENTS}
|
||||
```
|
||||
|
||||
## 版本号规范
|
||||
|
||||
遵循语义化版本(Semantic Versioning):`MAJOR.MINOR.PATCH`
|
||||
|
||||
| 变更类型 | 版本变化 | 示例 |
|
||||
|----------|----------|------|
|
||||
| 破坏性变更 | MAJOR +1 | `1.2.0` → `2.0.0` |
|
||||
| 向后兼容的新功能 | MINOR +1 | `1.1.0` → `1.2.0` |
|
||||
| 向后兼容的 Bug 修复 | PATCH +1 | `1.1.0` → `1.1.1` |
|
||||
|
||||
## API 注意事项
|
||||
|
||||
- `compare` API 无专用 shortcut,通过 `gitlink-cli api GET /:owner/:repo/compare/{head}...{base}` 调用
|
||||
- `compare` API 另支持查询参数格式:`GET /v1/:owner/:repo/compare.json?from=&to=`
|
||||
- `release +create` 的 `--body` 接受完整 Markdown,支持多行文本
|
||||
- `release +view` / `release +delete` 必须使用 `version_id`(从 `release +list` 获取),不可用 `tag_name`
|
||||
- 创建 Release 前务必让用户审核生成的 Notes 内容
|
||||
|
||||
## References
|
||||
|
||||
- [collect-data](references/collect-data.md) — 收集 commits、PR、Issue 数据
|
||||
- [classify-rules](references/classify-rules.md) — 变更分类规则详解
|
||||
- [generate-and-publish](references/generate-and-publish.md) — 生成 Notes 并发布
|
||||
- [full-workflow](examples/full-workflow.md) — 完整端到端示例
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
- [gitlink-release](../gitlink-release/SKILL.md) — Release 操作
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# Release Notes 完整生成示例
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
> **适用场景:** AI Agent 端到端生成 Release Notes,从数据收集到发布。
|
||||
|
||||
以 `zzx-coder/gitlink-cli` 项目从 `v1.0.0` 到 `v1.1.0` 为例。
|
||||
|
||||
## 完整流程
|
||||
|
||||
### 第一步:确定版本范围
|
||||
|
||||
```bash
|
||||
# 获取已有 release
|
||||
gitlink-cli release +list --format json
|
||||
|
||||
# 返回示例(截取关键字段):
|
||||
# {
|
||||
# "ok": true,
|
||||
# "data": {
|
||||
# "releases": [
|
||||
# { "tag_name": "v1.0.0", "created_at": "2026-05-01", ... },
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
# }
|
||||
|
||||
# AI 据此确定:PREV_VERSION = "v1.0.0",NEW_VERSION = "v1.1.0"
|
||||
```
|
||||
|
||||
### 第二步:收集 commits
|
||||
|
||||
```bash
|
||||
gitlink-cli api GET /:owner/:repo/compare/v1.0.0...v1.1.0 --format json
|
||||
|
||||
# 从返回中提取 commits 列表,每个 commit 含:
|
||||
# - commit.message (提交信息)
|
||||
# - commit.author.name (作者)
|
||||
# - sha (提交 SHA)
|
||||
```
|
||||
|
||||
### 第三步:收集已合并 PR
|
||||
|
||||
```bash
|
||||
gitlink-cli pr +list --state merged --format json
|
||||
|
||||
# AI 筛选 merged_at >= "2026-05-01"(v1.0.0 发布时间)的 PR
|
||||
# 提取每个 PR 的 title、number、author.login
|
||||
```
|
||||
|
||||
### 第四步:收集已关闭 Issue
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
|
||||
# AI 筛选 closed_at >= "2026-05-01" 的 Issue
|
||||
# 提取每个 Issue 的 subject、project_issues_index、issue_tags
|
||||
```
|
||||
|
||||
### 第五步:AI 分类
|
||||
|
||||
AI 根据 [分类规则](../references/classify-rules.md) 对收集到的数据分类:
|
||||
|
||||
```
|
||||
新功能:
|
||||
- 支持批量 Issue 操作 (#12) (@zhangsan)
|
||||
- 新增 uninstall 命令 (#11) (@lisi)
|
||||
|
||||
Bug 修复:
|
||||
- 修复 URL 解析异常 (#10) (@wangwu)
|
||||
|
||||
功能改进:
|
||||
- 重构自动部署配置 (camelliamc)
|
||||
|
||||
文档:
|
||||
- 更新分支映射说明 (camelliamc)
|
||||
```
|
||||
|
||||
### 第六步:生成 Notes 并确认
|
||||
|
||||
AI 套用标准模板生成草稿并展示给用户:
|
||||
|
||||
```markdown
|
||||
# 🎉 Release v1.1.0
|
||||
|
||||
## 📊 变更统计
|
||||
- **新功能**: 2 个
|
||||
- **Bug 修复**: 1 个
|
||||
- **功能改进**: 1 个
|
||||
- **破坏性变更**: 0 个
|
||||
|
||||
## ✨ 新功能
|
||||
- 支持批量 Issue 操作 (#12) (@zhangsan)
|
||||
- 新增 uninstall 命令 (#11) (@lisi)
|
||||
|
||||
## 🐛 Bug 修复
|
||||
- 修复 URL 解析异常 (#10) (@wangwu)
|
||||
|
||||
## 🔧 功能改进
|
||||
- 重构自动部署配置 (camelliamc)
|
||||
|
||||
## 🙏 贡献者
|
||||
zzx-coder, camelliamc
|
||||
|
||||
---
|
||||
**完整变更日志**: https://www.gitlink.org.cn/zzx-coder/gitlink-cli/compare/v1.0.0...v1.1.0
|
||||
```
|
||||
|
||||
### 第七步:用户确认后发布
|
||||
|
||||
```bash
|
||||
gitlink-cli release +create \
|
||||
--tag v1.1.0 \
|
||||
--name "v1.1.0" \
|
||||
--body "# 🎉 Release v1.1.0
|
||||
|
||||
## 📊 变更统计
|
||||
- **新功能**: 2 个
|
||||
- **Bug 修复**: 1 个
|
||||
- **功能改进**: 1 个
|
||||
- **破坏性变更**: 0 个
|
||||
|
||||
## ✨ 新功能
|
||||
- 支持批量 Issue 操作 (#12) (@zhangsan)
|
||||
- 新增 uninstall 命令 (#11) (@lisi)
|
||||
|
||||
## 🐛 Bug 修复
|
||||
- 修复 URL 解析异常 (#10) (@wangwu)
|
||||
|
||||
## 🔧 功能改进
|
||||
- 重构自动部署配置 (camelliamc)
|
||||
|
||||
## 🙏 贡献者
|
||||
zhangsan, lisi, wangwu, camelliamc
|
||||
|
||||
---
|
||||
**完整变更日志**: https://www.gitlink.org.cn/zzx-coder/gitlink-cli/compare/v1.0.0...v1.1.0"
|
||||
```
|
||||
|
||||
## AI Agent 执行要点
|
||||
|
||||
1. **自动解析 `--owner` / `--repo`**:在仓库目录下执行,CLI 自动从 git remote 解析
|
||||
2. **始终使用 `--format json`**:所有命令加此参数,便于 AI 解析返回值
|
||||
3. **时间筛选**:用上一个 Release 的 `created_at` 作为 PR/Issue 的时间筛选基线
|
||||
4. **去重**:PR 和 Issue 描述同一变更时合并为一条
|
||||
5. **确认优先**:生成 Notes 后必须展示给用户,收到确认才执行 `release +create`
|
||||
|
||||
## References
|
||||
|
||||
- [SKILL.md](../SKILL.md) — 工作流和模板总览
|
||||
- [collect-data](../references/collect-data.md) — 数据收集详细说明
|
||||
- [classify-rules](../references/classify-rules.md) — 分类规则
|
||||
- [generate-and-publish](../references/generate-and-publish.md) — 生成和发布
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# 变更分类规则
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
将收集到的 commits、PRs 和 Issues 按类型归类,为生成结构化 Release Notes 做准备。
|
||||
|
||||
## 分类维度
|
||||
|
||||
变更按以下维度分类:
|
||||
|
||||
| 类型 | 图标 | 标题 |
|
||||
|------|------|------|
|
||||
| 新功能 | ✨ | 新功能 |
|
||||
| Bug 修复 | 🐛 | Bug 修复 |
|
||||
| 功能改进 | 🔧 | 功能改进 |
|
||||
| 破坏性变更 | ⚠️ | 破坏性变更 |
|
||||
| 文档 | 📝 | 文档 |
|
||||
| 安全修复 | 🔒 | 安全修复 |
|
||||
|
||||
## 分类依据
|
||||
|
||||
### 按 Issue 标签分类(最可靠)
|
||||
|
||||
从 `issue +list` 返回的 `issue_tags` 字段匹配:
|
||||
|
||||
| Issue 标签 | 对应类型 |
|
||||
|------------|----------|
|
||||
| `feature`, `enhancement` | 新功能 |
|
||||
| `bug`, `fix` | Bug 修复 |
|
||||
| `improvement`, `optimize` | 功能改进 |
|
||||
| `breaking`, `major` | 破坏性变更 |
|
||||
| `docs`, `documentation` | 文档 |
|
||||
| `security`, `vulnerability` | 安全修复 |
|
||||
|
||||
### 按 Commit 关键词分类(辅助)
|
||||
|
||||
从 `compare` API 返回的 `commit.message` 第一行匹配:
|
||||
|
||||
| 关键词 | 对应类型 |
|
||||
|--------|----------|
|
||||
| `feat:`, `add`, `新增` | 新功能 |
|
||||
| `fix:`, `bugfix`, `修复` | Bug 修复 |
|
||||
| `improve:`, `optimize:`, `refactor:`, `优化`, `重构` | 功能改进 |
|
||||
| `BREAKING`, `breaking:`, `!` | 破坏性变更 |
|
||||
| `docs:`, `doc` | 文档 |
|
||||
| `security:`, `安全` | 安全修复 |
|
||||
|
||||
### 按 PR 标题分类(辅助)
|
||||
|
||||
PR 标题通常遵循 Conventional Commits 格式,按前缀匹配:
|
||||
|
||||
| PR 标题前缀 | 对应类型 |
|
||||
|-------------|----------|
|
||||
| `feat:` / `feature:` | 新功能 |
|
||||
| `fix:` | Bug 修复 |
|
||||
| `refactor:` / `perf:` | 功能改进 |
|
||||
| `docs:` | 文档 |
|
||||
|
||||
## 分类优先级
|
||||
|
||||
1. **Issue 标签**(最准确,优先采用)
|
||||
2. **PR 标题前缀**(次之)
|
||||
3. **Commit 关键词**(兜底)
|
||||
|
||||
对于同一个变更,如果 Issue 标签和 commit 关键词都在,以 Issue 标签为准。
|
||||
|
||||
## 去重
|
||||
|
||||
以下情况会产生重复条目,需去重:
|
||||
|
||||
- PR 和 Issue 关联同一个变更 → 合并为一条:`变更描述 (#PR编号, #Issue编号)`
|
||||
- 同一变更的多个 commit → 只保留摘要最清晰的一条
|
||||
- PR 标题与关联 Issue 主题高度相似 → 合并,优先使用 Issue 的 subject
|
||||
|
||||
## 输出格式
|
||||
|
||||
分类完成后,整理为结构化数据供模板填充:
|
||||
|
||||
```
|
||||
新功能:
|
||||
- 支持批量关闭 Issue (#123)
|
||||
- 新增 Wiki 管理命令 (#130)
|
||||
|
||||
Bug 修复:
|
||||
- 修复 Windows 登录 token 存储失败 (#118)
|
||||
|
||||
功能改进:
|
||||
- 优化 API 请求性能 (#125)
|
||||
|
||||
破坏性变更:
|
||||
- 重构认证模块接口(不向下兼容)(#140)
|
||||
|
||||
文档:
|
||||
- 补充分支映射说明 (#115)
|
||||
```
|
||||
|
||||
## 贡献者收集
|
||||
|
||||
从 commit 和 PR 数据中提取贡献者列表:
|
||||
|
||||
- Commit: `author.name` 或 `author.login`
|
||||
- PR: `author.login`
|
||||
|
||||
去重后生成贡献者名单,写入 Release Notes 末尾。
|
||||
|
||||
## References
|
||||
|
||||
- [collect-data](collect-data.md) — 数据收集步骤
|
||||
- [generate-and-publish](generate-and-publish.md) — 生成 Notes 并发布
|
||||
- [SKILL.md](../SKILL.md) — 分类规则速查表
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# 收集变更数据
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
收集 Release Notes 所需的三类数据:版本范围、commits、PRs 和 Issues。
|
||||
|
||||
## 命令
|
||||
|
||||
### 步骤 1:确定版本范围
|
||||
|
||||
```bash
|
||||
# 获取已有 release 列表,找到上一个版本 tag
|
||||
gitlink-cli release +list --format json
|
||||
# 从返回的 releases 中提取最后一个 tag_name 作为 PREV_VERSION
|
||||
# 用户指定或 AI 推断新版本号 NEW_VERSION
|
||||
```
|
||||
|
||||
### 步骤 2:获取 commits(平台 compare API)
|
||||
|
||||
```bash
|
||||
# 获取两个 tag 之间的 commit 比较
|
||||
gitlink-cli api GET /:owner/:repo/compare/{PREV_VERSION}...{NEW_VERSION} --format json
|
||||
|
||||
# 也可用查询参数格式
|
||||
gitlink-cli api GET /v1/:owner/:repo/compare.json --query "from={PREV_VERSION}&to={NEW_VERSION}" --format json
|
||||
```
|
||||
|
||||
返回数据包含:`commits`(提交列表含 message/author/date/sha)、`total_commits`(提交总数)、`files`(变更文件)等。
|
||||
|
||||
### 步骤 3:获取已合并的 PR
|
||||
|
||||
```bash
|
||||
# 获取已合并的 PR 列表
|
||||
gitlink-cli pr +list --state merged --format json
|
||||
|
||||
# 从返回的 PRs 中按 merged_at 时间筛选:
|
||||
# 只保留 merged_at >= 上一个版本发布时间的 PR
|
||||
```
|
||||
|
||||
返回数据包含:每个 PR 的 `title`、`number`、`author`、`merged_at`、`pull_request_number` 等。
|
||||
|
||||
### 步骤 4:获取已关闭的 Issue
|
||||
|
||||
```bash
|
||||
# 获取已关闭的 Issue 列表
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
|
||||
# 从返回的 Issues 中按 closed_at 时间筛选:
|
||||
# 只保留 closed_at >= 上一个版本发布时间的 Issue
|
||||
```
|
||||
|
||||
返回数据包含:每个 Issue 的 `subject`、`project_issues_index`、`issue_tags`(标签)、`author`、`closed_at` 等。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--format` | 否 | 始终建议 `json`,便于 AI 解析 |
|
||||
| `--state` | 否 | PR: `merged`;Issue: `closed` |
|
||||
| `--page` | 否 | 大量数据时分页获取 |
|
||||
| `--limit` | 否 | 每页条数 |
|
||||
|
||||
## 数据整合
|
||||
|
||||
收集完成后,AI 整合三类数据:
|
||||
|
||||
1. **Commits** → 提取 commit message 第一行作为变更摘要,附作者名
|
||||
2. **PRs** → 用 `title`、`number`、`author.login` 生成条目:`- 功能描述 (#PR编号) (@作者)`
|
||||
3. **Issues** → 用 `subject`、`project_issues_index`、`author.login` 生成条目:`- Issue 描述 (#编号) (@作者)`
|
||||
|
||||
时间筛选逻辑:从 `release +list` 获取上一个版本的发布时间,只取该时间之后的 PR/Issue。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 如果是**第一个版本**(无上一版本),只收集当前版本时间范围内的 PR/Issue,commits 用全量最近提交
|
||||
- `compare` API 的 tag 需要真实存在,否则返回 404
|
||||
- PR 和 Issue 的返回可能超过单页,注意分页获取全部数据
|
||||
- `--owner` / `--repo` 在仓库目录下可自动解析
|
||||
|
||||
## References
|
||||
|
||||
- [classify-rules](classify-rules.md) — 收集完成后对变更进行分类
|
||||
- [generate-and-publish](generate-and-publish.md) — 生成 Notes 并发布
|
||||
- [gitlink-release](../gitlink-release/SKILL.md) — Release 操作
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
# 生成并发布 Release Notes
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
> **CRITICAL — 此为写入操作,执行前务必确认用户已审核 Release Notes 内容。**
|
||||
|
||||
将分类整理后的变更数据套用模板,生成 Markdown 格式的 Release Notes,并发布到 GitLink。
|
||||
|
||||
## 模板
|
||||
|
||||
### 标准模板
|
||||
|
||||
```markdown
|
||||
# 🎉 Release {VERSION}
|
||||
|
||||
## 📊 变更统计
|
||||
- **新功能**: {FEATURE_COUNT} 个
|
||||
- **Bug 修复**: {BUG_FIX_COUNT} 个
|
||||
- **功能改进**: {ENHANCEMENT_COUNT} 个
|
||||
- **破坏性变更**: {BREAKING_COUNT} 个
|
||||
|
||||
## ✨ 新功能
|
||||
{FEATURES}
|
||||
|
||||
## 🐛 Bug 修复
|
||||
{BUG_FIXES}
|
||||
|
||||
## 🔧 功能改进
|
||||
{ENHANCEMENTS}
|
||||
|
||||
## ⚠️ 破坏性变更
|
||||
{BREAKING_CHANGES}
|
||||
|
||||
## 🙏 贡献者
|
||||
{CONTRIBUTORS}
|
||||
|
||||
---
|
||||
**完整变更日志**: https://www.gitlink.org.cn/{OWNER}/{REPO}/compare/{PREV}...{VERSION}
|
||||
```
|
||||
|
||||
### 简化模板(适用于 patch 版本或小型发布)
|
||||
|
||||
```markdown
|
||||
# {VERSION}
|
||||
|
||||
## 新增
|
||||
{FEATURES}
|
||||
|
||||
## 修复
|
||||
{BUG_FIXES}
|
||||
|
||||
## 改进
|
||||
{ENHANCEMENTS}
|
||||
|
||||
## 贡献者
|
||||
{CONTRIBUTORS}
|
||||
|
||||
[完整变更](https://www.gitlink.org.cn/{OWNER}/{REPO}/compare/{PREV}...{VERSION})
|
||||
```
|
||||
|
||||
## 模板占位符说明
|
||||
|
||||
| 占位符 | 来源 |
|
||||
|--------|------|
|
||||
| `{VERSION}` | 用户指定或 AI 推断的新版本号(如 `v1.2.0`) |
|
||||
| `{PREV}` | `release +list` 获取的上一个版本 tag |
|
||||
| `{OWNER}` | 仓库所有者,从 git remote 解析 |
|
||||
| `{REPO}` | 仓库名称,从 git remote 解析 |
|
||||
| `{FEATURE_COUNT}` 等 | 分类后的各类变更数量 |
|
||||
| `{FEATURES}` 等 | 分类后的各类变更条目,每条一行 `- 描述 (#编号) (@作者)` |
|
||||
| `{CONTRIBUTORS}` | 从 commits/PRs 去重后的贡献者列表 |
|
||||
|
||||
## 命令
|
||||
|
||||
### 新建 Release
|
||||
|
||||
```bash
|
||||
gitlink-cli release +create \
|
||||
--tag v1.2.0 \
|
||||
--name "v1.2.0" \
|
||||
--body "<Markdown 格式的 Release Notes>"
|
||||
```
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--tag` | **是** | 版本 tag(如 `v1.2.0`) |
|
||||
| `--name` | **是** | Release 名称,通常与 tag 一致 |
|
||||
| `--body` | 否 | Release Notes 正文(Markdown,支持多行) |
|
||||
| `--target` | 否 | 目标分支,默认 `master` |
|
||||
| `--prerelease` | 否 | 标记为预发布版本 |
|
||||
|
||||
### 更新已有 Release
|
||||
|
||||
```bash
|
||||
gitlink-cli release +update \
|
||||
--id <version_id> \
|
||||
--body "<更新后的 Release Notes>"
|
||||
```
|
||||
|
||||
> ⚠️ `release +update` 使用 `version_id`(数字 ID,从 `release +list` 获取),不是 `tag_name`。
|
||||
|
||||
## Workflow
|
||||
|
||||
> [!CAUTION]
|
||||
> Release Notes 发布是 **Write Operation**,执行前必须让用户审核内容。
|
||||
|
||||
1. **生成** Release Notes 草稿(套用模板填充数据)
|
||||
2. **展示**草稿给用户审核
|
||||
3. **确认**用户同意后才执行 `release +create` 或 `release +update`
|
||||
4. **报告**创建的 Release URL 给用户
|
||||
|
||||
## 发布前检查清单
|
||||
|
||||
- [ ] 版本号遵循语义化版本规范
|
||||
- [ ] 变更统计与实际一致
|
||||
- [ ] 破坏性变更已明确标注
|
||||
- [ ] 贡献者列表完整
|
||||
- [ ] 无敏感信息泄露
|
||||
- [ ] 对比链接可访问
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `release +create --body` 接受完整 Markdown,换行和格式由模板控制
|
||||
- `release +view` / `release +delete` 使用 `version_id`(数字),不是 `tag_name`
|
||||
- 可用 `release +update --body` 修正已发布的 Notes
|
||||
- 首个版本(无上一版本)省略对比链接
|
||||
|
||||
## References
|
||||
|
||||
- [collect-data](collect-data.md) — 收集变更数据
|
||||
- [classify-rules](classify-rules.md) — 变更分类规则
|
||||
- [full-workflow](../examples/full-workflow.md) — 完整端到端示例
|
||||
- [gitlink-release](../../gitlink-release/SKILL.md) — Release 操作
|
||||
- [release +create](../../gitlink-release/references/gitlink-release-create.md) — 创建 Release 详细参数
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
# gitlink-code-review - 智能代码审查 Skill
|
||||
|
||||
[](https://www.gitlink.org.cn/zzx-coder/gitlink-cli)
|
||||
[](SKILL.md)
|
||||
[](SKILL.md)
|
||||
|
||||
欢迎使用 **gitlink-code-review** Skill!这是一个 AI 驱动的自动化代码审查工具,帮助开发者和 Reviewers 快速分析 GitLink PR 的代码质量。
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
### 核心功能
|
||||
|
||||
- ✅ **自动代码分析**:获取 PR 的文件列表和 diff 内容
|
||||
- ✅ **多维度审查**:代码质量、安全性、性能、可维护性
|
||||
- ✅ **结构化报告**:生成 JSON/Markdown 格式的审查报告
|
||||
- ✅ **智能建议**:提供具体的代码修改建议
|
||||
- ✅ **自动评论**:将审查意见自动添加为 PR 评论
|
||||
- ✅ **AI 驱动**:基于 Claude 的代码理解能力
|
||||
|
||||
### 审查维度
|
||||
|
||||
| 维度 | 检查项 | 说明 |
|
||||
|------|--------|------|
|
||||
| **代码质量** | 复杂度、命名规范、注释完整性 | 确保代码清晰易读 |
|
||||
| **安全性** | SQL 注入、XSS、敏感信息泄露 | 发现安全漏洞 |
|
||||
| **性能** | 资源泄漏、循环效率、数据库查询 | 优化性能问题 |
|
||||
| **可维护性** | 代码重复、职责单一、测试覆盖 | 提高代码可维护性 |
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 前置条件
|
||||
|
||||
1. **安装 gitlink-cli**
|
||||
```bash
|
||||
npm install -g @gitlink-ai/cli
|
||||
```
|
||||
|
||||
2. **配置认证**
|
||||
```bash
|
||||
gitlink-cli auth login
|
||||
```
|
||||
|
||||
3. **验证安装**
|
||||
```bash
|
||||
gitlink-cli pr +list
|
||||
```
|
||||
|
||||
### 基础使用
|
||||
|
||||
#### 1. 获取 PR 信息
|
||||
|
||||
```bash
|
||||
# 查看 PR 详情
|
||||
gitlink-cli pr +view --id 123 --format json
|
||||
|
||||
# 获取变更文件列表
|
||||
gitlink-cli pr +files --id 123 --format json
|
||||
|
||||
# 获取 diff 内容
|
||||
gitlink-cli pr +diff --id 123 --format json
|
||||
```
|
||||
|
||||
#### 2. 进行代码审查
|
||||
|
||||
**AI Agent 方式**(推荐):
|
||||
|
||||
```
|
||||
用户: "帮我审查 PR #123,检查代码质量、安全性和性能问题"
|
||||
|
||||
AI Agent 将:
|
||||
1. 获取 PR 的代码变更
|
||||
2. 分析代码质量和潜在问题
|
||||
3. 生成结构化的审查报告
|
||||
4. (可选)自动添加审查评论
|
||||
```
|
||||
|
||||
**手动方式**:
|
||||
|
||||
```bash
|
||||
# 获取 diff 并分析
|
||||
gitlink-cli pr +diff --id 123 --format json > pr_diff.json
|
||||
|
||||
# 使用 AI 工具分析 pr_diff.json
|
||||
# 生成审查报告
|
||||
|
||||
# (可选)添加评论到 PR
|
||||
gitlink-cli api POST /:owner/:repo/pulls/123/reviews --body '{
|
||||
"body": "审查报告内容...",
|
||||
"event": "COMMENT"
|
||||
}'
|
||||
```
|
||||
|
||||
### 完整工作流示例
|
||||
|
||||
详见 [`examples/comprehensive-review-workflow.md`](examples/comprehensive-review-workflow.md)
|
||||
|
||||
## 📊 审查报告示例
|
||||
|
||||
### 简化版报告
|
||||
|
||||
```markdown
|
||||
# 代码审查报告
|
||||
|
||||
## 总体评分: 85/100 ⭐⭐⭐⭐
|
||||
|
||||
## 🔴 高优先级问题(2)
|
||||
|
||||
1. **敏感信息泄露** - `src/auth/login.go:45`
|
||||
- 硬编码的密钥不应出现在代码中
|
||||
- 建议:使用环境变量存储密钥
|
||||
|
||||
2. **资源泄漏** - `src/auth/login.go:78`
|
||||
- 数据库连接未关闭
|
||||
- 建议:使用 defer 确保连接关闭
|
||||
|
||||
## ⭐ 优秀实践(1)
|
||||
|
||||
1. **优秀的错误处理** - `src/auth/user.go:120`
|
||||
```
|
||||
|
||||
### 完整版报告
|
||||
|
||||
完整版报告包含:
|
||||
- PR 基本信息
|
||||
- 各维度详细评分
|
||||
- 按优先级排序的问题列表
|
||||
- 具体的代码位置和修改建议
|
||||
- 优秀实践和改进建议
|
||||
- 逐文件的详细分析
|
||||
|
||||
## 🎯 使用场景
|
||||
|
||||
### 场景 1:开发者自审
|
||||
|
||||
开发者在提交 PR 前进行自审:
|
||||
```bash
|
||||
# 获取 PR diff
|
||||
gitlink-cli pr +diff --id 123 --format json
|
||||
|
||||
# AI 分析并生成报告
|
||||
# 修复发现的问题
|
||||
```
|
||||
|
||||
### 场景 2:Reviewers 辅助审查
|
||||
|
||||
Reviewers 使用 AI 辅助审查:
|
||||
```bash
|
||||
# 快速获取审查报告
|
||||
gitlink-cli pr +view --id 123 --format json
|
||||
gitlink-cli pr +diff --id 123 --format json
|
||||
|
||||
# AI 生成报告,Reviewers 参考
|
||||
# 专注于业务逻辑和架构设计
|
||||
```
|
||||
|
||||
### 场景 3:CI/CD 集成
|
||||
|
||||
在 CI/CD 流程中自动审查:
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
code_review:
|
||||
script:
|
||||
- gitlink-cli pr +diff --id $MR_ID --format json
|
||||
- ai-code-review --input pr_diff.json --output report.json
|
||||
- check-score --min 70 report.json
|
||||
```
|
||||
|
||||
### 场景 4:新贡献者指导
|
||||
|
||||
为新贡献者的 PR 提供详细指导:
|
||||
```bash
|
||||
# 全面审查新贡献者的 PR
|
||||
# 提供详细的代码指导
|
||||
# 帮助改进代码质量
|
||||
```
|
||||
|
||||
## 📚 文档导航
|
||||
|
||||
- **[SKILL.md](SKILL.md)** - 技能总览和完整功能说明
|
||||
- **[REFERENCE.md](REFERENCE.md)** - API 详细参考
|
||||
- **[references/](references/)** - 详细操作指南
|
||||
- **[examples/](examples/)** - 工作流示例
|
||||
|
||||
### 快速查找
|
||||
|
||||
- **我想了解基本用法**: [SKILL.md](SKILL.md#使用方式)
|
||||
- **我想看审查维度**: [SKILL.md](SKILL.md#审查维度)
|
||||
- **我想看工作流示例**: [examples/comprehensive-review-workflow.md](examples/comprehensive-review-workflow.md)
|
||||
- **我想了解 API 细节**: [REFERENCE.md](REFERENCE.md)
|
||||
|
||||
## 🤖 AI Agent 使用
|
||||
|
||||
Claude Code 和其他 AI Agent 可以直接使用此 Skill:
|
||||
|
||||
```
|
||||
用户请求: "审查 PR #123"
|
||||
↓
|
||||
AI 读取 gitlink-code-review/SKILL.md
|
||||
↓
|
||||
AI 执行:
|
||||
1. gitlink-cli pr +view --id 123 --format json
|
||||
2. gitlink-cli pr +files --id 123 --format json
|
||||
3. gitlink-cli pr +diff --id 123 --format json
|
||||
↓
|
||||
AI 分析代码变更
|
||||
↓
|
||||
AI 生成结构化审查报告
|
||||
↓
|
||||
AI (可选)添加评论到 PR
|
||||
↓
|
||||
完成!
|
||||
```
|
||||
|
||||
### 典型 AI 对话
|
||||
|
||||
**用户**: "帮我审查一下 PR #123,重点关注安全性问题"
|
||||
|
||||
**AI Agent**:
|
||||
```
|
||||
我来帮您审查 PR #123,重点关注安全性问题。
|
||||
|
||||
[获取 PR 信息...]
|
||||
[分析代码变更...]
|
||||
|
||||
发现以下安全问题:
|
||||
|
||||
🔴 高优先级(1):
|
||||
1. SQL 注入风险 - src/auth/login.go:45
|
||||
- 直接拼接用户输入到 SQL 语句
|
||||
- 建议:使用参数化查询
|
||||
|
||||
⚠️ 中优先级(1):
|
||||
1. 缺少输入验证 - src/auth/login.go:30
|
||||
- 未验证用户名长度和格式
|
||||
- 建议:添加输入验证中间件
|
||||
|
||||
总体评分:70/100
|
||||
安全性评分:60/100 ⚠️
|
||||
|
||||
需要我详细说明修复方法吗?
|
||||
```
|
||||
|
||||
## 🎓 最佳实践
|
||||
|
||||
### 审查时机
|
||||
|
||||
1. **PR 创建后**:立即进行初步审查
|
||||
2. **PR 更新后**:审查新增的代码变更
|
||||
3. **合并前**:最终审查确认代码质量
|
||||
|
||||
### 审查重点
|
||||
|
||||
根据 PR 类型调整审查重点:
|
||||
- **功能 PR**:代码质量 + 可维护性
|
||||
- **Bug 修复**:修复完整性 + 测试覆盖
|
||||
- **重构 PR**:性能改进 + 代码简化
|
||||
- **文档 PR**:文档完整性 + 准确性
|
||||
|
||||
### 评论规范
|
||||
|
||||
- ✅ **建设性**:提供具体的修改建议
|
||||
- ✅ **礼貌友好**:使用积极的语言
|
||||
- ✅ **解释原因**:说明为什么需要修改
|
||||
- ✅ **认可优点**:指出优秀实践
|
||||
|
||||
### 自动化审查
|
||||
|
||||
配置 CI/CD 自动审查:
|
||||
```yaml
|
||||
# 合并门禁示例
|
||||
if (review_score < 70) {
|
||||
block_merge("代码审查评分低于 70 分")
|
||||
}
|
||||
if (high_priority_issues > 0) {
|
||||
block_merge("存在高优先级问题")
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 质量标准
|
||||
|
||||
### 审查评分体系
|
||||
|
||||
| 分数范围 | 等级 | 说明 |
|
||||
|---------|------|------|
|
||||
| 90-100 | ⭐⭐⭐⭐⭐ 优秀 | 代码质量高,可以直接合并 |
|
||||
| 75-89 | ⭐⭐⭐⭐ 良好 | 代码质量良好,小幅改进后可合并 |
|
||||
| 60-74 | ⭐⭐⭐ 一般 | 存在一些问题,建议改进后合并 |
|
||||
| < 60 | ⭐⭐ 较差 | 存在严重问题,必须修复 |
|
||||
|
||||
### 问题优先级
|
||||
|
||||
| 优先级 | 图标 | 说明 | 是否阻止合并 |
|
||||
|--------|------|------|--------------|
|
||||
| HIGH | 🔴 | 安全漏洞、严重性能问题 | 是 |
|
||||
| MEDIUM | ⚠️ | 代码质量问题、潜在风险 | 建议 |
|
||||
| LOW | ℹ️ | 代码风格、轻微改进 | 否 |
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q: 如何提高审查准确性?
|
||||
|
||||
**A**:
|
||||
1. 提供完整的 diff 内容
|
||||
2. 根据项目类型调整审查规则
|
||||
3. 结合项目上下文分析
|
||||
4. 定期更新审查规则
|
||||
|
||||
### Q: 如何处理误报?
|
||||
|
||||
**A**:
|
||||
1. AI 审查可能产生误报,需要人工验证
|
||||
2. 可以配置白名单忽略特定规则
|
||||
3. 提供反馈改进审查规则
|
||||
|
||||
### Q: 审查报告可以作为合并条件吗?
|
||||
|
||||
**A**:
|
||||
1. 可以将审查评分设置为合并门禁
|
||||
2. 建议设置最低评分(如 70 分)
|
||||
3. 高优先级问题必须修复后才能合并
|
||||
|
||||
### Q: 如何集成到 CI/CD?
|
||||
|
||||
**A**:
|
||||
参考 [`examples/ci-integration.md`](examples/ci-integration.md) 中的配置示例
|
||||
|
||||
## 🔗 相关资源
|
||||
|
||||
- [gitlink-cli 主项目](https://www.gitlink.org.cn/zzx-coder/gitlink-cli)
|
||||
- [gitlink-pr Skill](../gitlink-pr/SKILL.md) - PR 操作指南
|
||||
- [gitlink-workflow Skill](../gitlink-workflow/SKILL.md) - AI 工作流
|
||||
- [代码审查最佳实践](https://google.github.io/eng-practices/review/)
|
||||
|
||||
## 📈 更新日志
|
||||
|
||||
### v1.0.0 (2026-06-12)
|
||||
|
||||
- ✅ 初始版本发布
|
||||
- ✅ 支持代码质量、安全性、性能、可维护性审查
|
||||
- ✅ 生成结构化审查报告
|
||||
- ✅ AI Agent 集成
|
||||
- ✅ 完整文档和示例
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎贡献!如果你有改进建议或发现问题,请:
|
||||
|
||||
1. 创建 Issue 描述问题或建议
|
||||
2. 提交 Pull Request 改进 Skill
|
||||
3. 分享你的使用经验
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
- **查看文档**: [SKILL.md](SKILL.md)
|
||||
- **查看示例**: [examples/](examples/)
|
||||
- **提交问题**: [GitLink Issues](https://www.gitlink.org.cn/zzx-coder/gitlink-cli/issues)
|
||||
|
||||
---
|
||||
|
||||
**祝你审查愉快!🚀**
|
||||
|
||||
如有问题,请查看 [SKILL.md](SKILL.md) 或 [examples/](examples/) 中的详细示例。
|
||||
|
|
@ -0,0 +1,579 @@
|
|||
# gitlink-code-review API 参考文档
|
||||
|
||||
本文档提供 gitlink-code-review Skill 的详细 API 参考和参数说明。
|
||||
|
||||
## 📋 目录
|
||||
|
||||
- [PR 信息获取 API](#pr-信息获取-api)
|
||||
- [代码分析 API](#代码分析-api)
|
||||
- [审查报告生成 API](#审查报告生成-api)
|
||||
- [评论集成 API](#评论集成-api)
|
||||
- [错误处理](#错误处理)
|
||||
- [数据格式](#数据格式)
|
||||
|
||||
---
|
||||
|
||||
## PR 信息获取 API
|
||||
|
||||
### 1. 获取 PR 详情
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +view --id <pr_id> --format json
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `--id` (必需): PR 编号
|
||||
- `--owner`: 仓库所有者(可选,自动从 git remote 解析)
|
||||
- `--repo`: 仓库名称(可选,自动从 git remote 解析)
|
||||
- `--format`: 输出格式(json/table/yaml)
|
||||
|
||||
**返回格式**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"id": 123,
|
||||
"project_issues_index": 123,
|
||||
"title": "Feature: Add user authentication",
|
||||
"body": "This PR adds user authentication...",
|
||||
"author": {
|
||||
"login": "developer",
|
||||
"user_id": 456
|
||||
},
|
||||
"status": "open",
|
||||
"pull_request_status": 0,
|
||||
"head": "feature/auth",
|
||||
"base": "main",
|
||||
"created_at": "2026-06-12T10:00:00Z",
|
||||
"updated_at": "2026-06-12T10:30:00Z"
|
||||
},
|
||||
"meta": {
|
||||
"identity": "user:developer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `id`: PR 数据库 ID
|
||||
- `project_issues_index`: PR 编号(网页 URL 中显示)
|
||||
- `pull_request_status`: PR 状态(0=open, 1=merged, 2=closed)
|
||||
|
||||
### 2. 获取变更文件列表
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +files --id <pr_id> --format json
|
||||
```
|
||||
|
||||
**返回格式**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"files": [
|
||||
{
|
||||
"filename": "src/auth/login.go",
|
||||
"status": "modified",
|
||||
"additions": 50,
|
||||
"deletions": 20,
|
||||
"changes": 70,
|
||||
"patch": "@@ -1,10 +1,15 @@\n+func login() {"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `status`: 文件状态(added/modified/deleted/renamed)
|
||||
- `additions`: 新增行数
|
||||
- `deletions`: 删除行数
|
||||
- `changes`: 总变更行数
|
||||
- `patch`: diff 片段
|
||||
|
||||
### 3. 获取 diff 内容
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +diff --id <pr_id> --format json
|
||||
```
|
||||
|
||||
**返回格式**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"diff": "diff --git a/src/auth/login.go b/src/auth/login.go\n@@ -1,10 +1,15 @@\n+func login() {",
|
||||
"files_count": 5,
|
||||
"additions": 150,
|
||||
"deletions": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 代码分析 API
|
||||
|
||||
代码分析由 AI Agent 执行,使用 Claude 的代码理解能力。
|
||||
|
||||
### 分析流程
|
||||
|
||||
1. **解析 diff 内容**
|
||||
2. **识别变更的代码块**
|
||||
3. **多维度分析代码**
|
||||
4. **生成结构化报告**
|
||||
|
||||
### 分析维度
|
||||
|
||||
#### 1. 代码质量分析
|
||||
|
||||
**检查项**:
|
||||
- 圈复杂度(Cyclomatic Complexity)
|
||||
- 函数长度
|
||||
- 嵌套层级
|
||||
- 命名规范
|
||||
- 注释完整性
|
||||
|
||||
**输出示例**:
|
||||
```json
|
||||
{
|
||||
"quality_analysis": {
|
||||
"overall_score": 90,
|
||||
"complexity": {
|
||||
"avg_cyclomatic_complexity": 3.5,
|
||||
"max_function_length": 50,
|
||||
"max_nesting_level": 3
|
||||
},
|
||||
"naming": {
|
||||
"score": 95,
|
||||
"issues": []
|
||||
},
|
||||
"comments": {
|
||||
"score": 85,
|
||||
"coverage": 75
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 安全性分析
|
||||
|
||||
**检查项**:
|
||||
- SQL 注入
|
||||
- XSS 漏洞
|
||||
- 敏感信息泄露
|
||||
- 认证问题
|
||||
- 输入验证
|
||||
|
||||
**输出示例**:
|
||||
```json
|
||||
{
|
||||
"security_analysis": {
|
||||
"overall_score": 75,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "HIGH",
|
||||
"rule": "SQL Injection",
|
||||
"file": "src/auth/login.go",
|
||||
"line": 45,
|
||||
"description": "直接拼接用户输入到 SQL 语句",
|
||||
"code": "query := \"SELECT * FROM users WHERE username = '\" + username + \"'\"",
|
||||
"suggestion": "使用参数化查询或 ORM"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 性能分析
|
||||
|
||||
**检查项**:
|
||||
- 循环效率
|
||||
- 资源泄漏
|
||||
- 数据库查询
|
||||
- 内存使用
|
||||
|
||||
**输出示例**:
|
||||
```json
|
||||
{
|
||||
"performance_analysis": {
|
||||
"overall_score": 80,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "MEDIUM",
|
||||
"rule": "Resource Leak",
|
||||
"file": "src/auth/login.go",
|
||||
"line": 78,
|
||||
"description": "数据库连接未关闭",
|
||||
"code": "db, _ := sql.Open(\"mysql\", dsn)",
|
||||
"suggestion": "使用 defer db.Close()"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 可维护性分析
|
||||
|
||||
**检查项**:
|
||||
- 代码重复
|
||||
- 职责单一
|
||||
- 依赖耦合
|
||||
- 测试覆盖
|
||||
|
||||
**输出示例**:
|
||||
```json
|
||||
{
|
||||
"maintainability_analysis": {
|
||||
"overall_score": 85,
|
||||
"duplicate_code_rate": 5,
|
||||
"test_coverage": 60,
|
||||
"recommendations": [
|
||||
"建议添加单元测试覆盖登录逻辑"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 审查报告生成 API
|
||||
|
||||
### JSON 格式报告
|
||||
|
||||
**结构**:
|
||||
```json
|
||||
{
|
||||
"pr_info": {
|
||||
"id": 123,
|
||||
"title": "Feature: Add user authentication",
|
||||
"author": "developer",
|
||||
"files_changed": 5,
|
||||
"lines_added": 150,
|
||||
"lines_removed": 50
|
||||
},
|
||||
"analysis_timestamp": "2026-06-12T10:30:00Z",
|
||||
"overall_assessment": {
|
||||
"total_score": 85,
|
||||
"quality_score": 90,
|
||||
"security_score": 75,
|
||||
"performance_score": 80,
|
||||
"maintainability_score": 85,
|
||||
"status": "APPROVED_WITH_CHANGES"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"id": 1,
|
||||
"file": "src/auth/login.go",
|
||||
"line": 45,
|
||||
"severity": "HIGH",
|
||||
"category": "security",
|
||||
"rule": "SQL Injection",
|
||||
"description": "直接拼接用户输入到 SQL 语句",
|
||||
"code_snippet": "query := \"SELECT * FROM users WHERE username = '\" + username + \"'\"",
|
||||
"suggestion": "使用参数化查询或 ORM",
|
||||
"references": [
|
||||
"https://owasp.org/www-community/attacks/SQL_Injection"
|
||||
]
|
||||
}
|
||||
],
|
||||
"positive_notes": [
|
||||
{
|
||||
"file": "src/auth/user.go",
|
||||
"line": 120,
|
||||
"description": "优秀的错误处理",
|
||||
"code_snippet": "if err != nil {\n log.Errorf(\"Failed to login: %v\", err)\n return err\n}"
|
||||
}
|
||||
],
|
||||
"recommendations": [
|
||||
"建议添加单元测试覆盖登录逻辑",
|
||||
"建议使用参数化查询防止 SQL 注入",
|
||||
"建议添加输入验证中间件"
|
||||
],
|
||||
"summary": "代码整体质量良好,但存在几个需要修复的安全问题。建议修复高优先级问题后合并。"
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown 格式报告
|
||||
|
||||
**模板**:
|
||||
```markdown
|
||||
# 代码审查报告
|
||||
|
||||
## PR 信息
|
||||
- **PR ID**: 123
|
||||
- **标题**: Feature: Add user authentication
|
||||
- **作者**: @developer
|
||||
- **分支**: feature/auth → main
|
||||
- **变更**: 5 个文件,+150 / -50 行
|
||||
|
||||
## 总体评分: 85/100 ⭐⭐⭐⭐
|
||||
|
||||
### 评分详情
|
||||
- 代码质量: 90/100
|
||||
- 安全性: 75/100 ⚠️
|
||||
- 性能: 80/100
|
||||
- 可维护性: 85/100
|
||||
|
||||
## 问题列表
|
||||
|
||||
### 🔴 高优先级(2)
|
||||
|
||||
#### 1. SQL 注入风险
|
||||
- **文件**: `src/auth/login.go:45`
|
||||
- **类别**: security
|
||||
- **问题**: 直接拼接用户输入到 SQL 语句
|
||||
- **代码**:
|
||||
```go
|
||||
query := "SELECT * FROM users WHERE username = '" + username + "'"
|
||||
```
|
||||
- **建议**: 使用参数化查询或 ORM
|
||||
|
||||
#### 2. 资源泄漏
|
||||
- **文件**: `src/auth/login.go:78`
|
||||
- **类别**: performance
|
||||
- **问题**: 数据库连接未关闭
|
||||
- **代码**:
|
||||
```go
|
||||
db, _ := sql.Open("mysql", dsn)
|
||||
// 缺少 defer db.Close()
|
||||
```
|
||||
- **建议**: 使用 `defer db.Close()`
|
||||
|
||||
### ⚠️ 中优先级(1)
|
||||
|
||||
#### 1. 缺少输入验证
|
||||
- **文件**: `src/auth/login.go:30`
|
||||
- **类别**: security
|
||||
- **问题**: 未验证用户名长度和格式
|
||||
- **建议**: 添加输入验证中间件
|
||||
|
||||
## ⭐ 优秀实践(1)
|
||||
|
||||
### 1. 优秀的错误处理
|
||||
- **文件**: `src/auth/user.go:120`
|
||||
- **描述**: 完善的错误处理和日志记录
|
||||
|
||||
## 💡 改进建议
|
||||
|
||||
1. 建议添加单元测试覆盖登录逻辑
|
||||
2. 建议使用参数化查询防止 SQL 注入
|
||||
3. 建议添加输入验证中间件
|
||||
4. 建议添加代码注释说明复杂逻辑
|
||||
|
||||
## 📊 文件详情
|
||||
|
||||
### src/auth/login.go
|
||||
- **变更**: +50 / -20 行
|
||||
- **问题**: 3 个(1 个高优先级,2 个中优先级)
|
||||
- **建议**: 修复安全问题,添加输入验证
|
||||
|
||||
### src/auth/user.go
|
||||
- **变更**: +80 / -10 行
|
||||
- **问题**: 1 个中优先级
|
||||
- **优秀实践**: 1 个
|
||||
|
||||
## 📝 总结
|
||||
|
||||
代码整体质量良好,结构清晰,命名规范。但存在几个需要修复的安全问题,特别是 SQL 注入风险。建议修复高优先级问题后合并。
|
||||
|
||||
**审查结果**: ✅ 建议修改后合并
|
||||
|
||||
---
|
||||
*报告生成时间: 2026-06-12 10:30:00 UTC*
|
||||
*审查工具: gitlink-code-review v1.0.0*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 评论集成 API
|
||||
|
||||
### 添加总评
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{
|
||||
"body": "<审查报告内容>",
|
||||
"event": "COMMENT"
|
||||
}'
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `:owner`: 仓库所有者
|
||||
- `:repo`: 仓库名称
|
||||
- `:id`: PR 编号
|
||||
- `body`: 评论内容(Markdown 格式)
|
||||
- `event`: 事件类型(COMMENT/APPROVE/REQUEST_CHANGES)
|
||||
|
||||
**事件类型**:
|
||||
- `COMMENT`: 普通评论
|
||||
- `APPROVE`: 批准 PR
|
||||
- `REQUEST_CHANGES`: 请求修改
|
||||
|
||||
### 添加行内评论
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli api POST /:owner/:repo/pulls/:id/comments --body '{
|
||||
"body": "建议使用参数化查询",
|
||||
"commit_id": "<commit_sha>",
|
||||
"path": "src/auth/login.go",
|
||||
"position": 45
|
||||
}'
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- `commit_id`: 提交 SHA
|
||||
- `path`: 文件路径
|
||||
- `position`: 行号
|
||||
- `body`: 评论内容
|
||||
|
||||
### 批量添加评论
|
||||
|
||||
**脚本示例**:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# 批量添加审查评论
|
||||
|
||||
PR_ID=123
|
||||
OWNER="myuser"
|
||||
REPO="myrepo"
|
||||
|
||||
# 读取审查报告中的问题
|
||||
issues=$(jq -r '.issues[]' review.json)
|
||||
|
||||
# 逐个添加评论
|
||||
for issue in $issues; do
|
||||
file=$(echo $issue | jq -r '.file')
|
||||
line=$(echo $issue | jq -r '.line')
|
||||
suggestion=$(echo $issue | jq -r '.suggestion')
|
||||
|
||||
gitlink-cli api POST /$OWNER/$REPO/pulls/$PR_ID/comments --body "{
|
||||
\"body\": \"$suggestion\",
|
||||
\"path\": \"$file\",
|
||||
\"position\": $line
|
||||
}"
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误
|
||||
|
||||
#### 1. PR 不存在
|
||||
|
||||
**错误信息**:
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"error": {
|
||||
"code": 404,
|
||||
"message": "PR not found",
|
||||
"suggestion": "检查 PR 编号是否正确"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**处理方法**:
|
||||
- 检查 PR 编号是否正确
|
||||
- 确认 PR 是否在正确的仓库中
|
||||
- 使用 `gitlink-cli pr +list` 验证 PR 存在
|
||||
|
||||
#### 2. 权限不足
|
||||
|
||||
**错误信息**:
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"error": {
|
||||
"code": 403,
|
||||
"message": "Permission denied",
|
||||
"suggestion": "确认账号有此仓库的访问权限"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**处理方法**:
|
||||
- 确认账号有仓库访问权限
|
||||
- 私有仓库需要先认证
|
||||
- 运行 `gitlink-cli auth login` 重新登录
|
||||
|
||||
#### 3. 未认证
|
||||
|
||||
**错误信息**:
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"error": {
|
||||
"code": 401,
|
||||
"message": "Unauthorized",
|
||||
"suggestion": "运行 gitlink-cli auth login 登录"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**处理方法**:
|
||||
- 运行 `gitlink-cli auth login` 登录
|
||||
- 或设置 `GITLINK_TOKEN` 环境变量
|
||||
|
||||
### 错误处理最佳实践
|
||||
|
||||
1. **检查 PR 状态**: 在审查前确认 PR 存在且可访问
|
||||
2. **验证权限**: 确认账号有仓库访问权限
|
||||
3. **处理网络错误**: 重试失败的请求
|
||||
4. **记录错误**: 记录错误日志以便调试
|
||||
|
||||
---
|
||||
|
||||
## 数据格式
|
||||
|
||||
### PR 状态映射
|
||||
|
||||
| 状态码 | 状态名称 | 说明 |
|
||||
|--------|---------|------|
|
||||
| 0 | open | 开放中 |
|
||||
| 1 | merged | 已合并 |
|
||||
| 2 | closed | 已关闭 |
|
||||
|
||||
### 严重性级别
|
||||
|
||||
| 级别 | 图标 | 说明 | 是否阻止合并 |
|
||||
|------|------|------|--------------|
|
||||
| CRITICAL | 🚨 | 严重问题,必须立即修复 | 是 |
|
||||
| HIGH | 🔴 | 高优先级,建议尽快修复 | 是 |
|
||||
| MEDIUM | ⚠️ | 中优先级,建议修复 | 建议 |
|
||||
| LOW | ℹ️ | 低优先级,可选修复 | 否 |
|
||||
| INFO | 💡 | 信息性建议 | 否 |
|
||||
|
||||
### 审查结果状态
|
||||
|
||||
| 状态 | 说明 | 是否可合并 |
|
||||
|------|------|-----------|
|
||||
| APPROVED | 批准,可直接合并 | 是 |
|
||||
| APPROVED_WITH_CHANGES | 批准,但建议修改 | 是 |
|
||||
| CHANGES_REQUESTED | 请求修改,需修复后重新审查 | 否 |
|
||||
| COMMENTED | 仅评论,未给出审批意见 | 待定 |
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关资源
|
||||
|
||||
- [gitlink-pr/SKILL.md](../gitlink-pr/SKILL.md) - PR 操作指南
|
||||
- [gitlink-shared/SKILL.md](../gitlink-shared/SKILL.md) - 认证和全局参数
|
||||
- [GitLink API 文档](https://www.gitlink.org.cn/api/docs) - 完整 API 参考
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
- **命令帮助**: `gitlink-cli pr --help`
|
||||
- **故障排查**: [../gitlink-shared/TROUBLESHOOTING.md](../gitlink-shared/TROUBLESHOOTING.md)
|
||||
- **API 参考**: [GitLink API 文档](https://www.gitlink.org.cn/api/docs)
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
---
|
||||
name: gitlink-code-review
|
||||
version: 1.0.0
|
||||
description: "智能代码审查:自动分析 PR 代码变更,进行多维度代码质量检查,生成结构化审查报告并自动添加评论。当用户需要对 GitLink PR 进行代码审查时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli pr --help"
|
||||
---
|
||||
|
||||
# gitlink-code-review(智能代码审查)
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 和 [`../gitlink-pr/SKILL.md`](../gitlink-pr/SKILL.md)
|
||||
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
|
||||
本技能提供 AI 驱动的自动化代码审查功能,帮助开发者和 Reviewers 快速分析 PR 代码质量。
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
| 功能 | 说明 | 需要认证 |
|
||||
|------|------|----------|
|
||||
| `代码变更分析` | 获取 PR 的文件列表和 diff 内容 | 否(公开项目) |
|
||||
| `代码质量检查` | 检查代码复杂度、命名规范、注释完整性 | 否 |
|
||||
| `安全性检查` | 检查 SQL 注入、XSS、敏感信息泄露等 | 否 |
|
||||
| `性能检查` | 识别性能反模式和资源泄漏 | 否 |
|
||||
| `可维护性检查` | 检查代码重复和职责单一原则 | 否 |
|
||||
| `审查报告生成` | 生成结构化的审查报告(JSON/Markdown) | 否 |
|
||||
| `自动评论` | 将审查意见自动添加为 PR 评论 | 是 |
|
||||
|
||||
## 📊 审查维度
|
||||
|
||||
### 1. 代码质量(Code Quality)
|
||||
|
||||
检查项:
|
||||
- **代码复杂度**:圈复杂度、嵌套层级、函数长度
|
||||
- **命名规范**:变量/函数/类的命名是否清晰
|
||||
- **注释完整性**:复杂逻辑是否有注释说明
|
||||
- **代码格式**:缩进、空行、代码组织
|
||||
|
||||
### 2. 安全性(Security)
|
||||
|
||||
检查项:
|
||||
- **SQL 注入**:字符串拼接 SQL 语句
|
||||
- **XSS 漏洞**:未转义的用户输入输出
|
||||
- **敏感信息**:硬编码的密码/密钥/Token
|
||||
- **认证问题**:权限检查、会话管理
|
||||
- **输入验证**:用户输入是否充分验证
|
||||
|
||||
### 3. 性能(Performance)
|
||||
|
||||
检查项:
|
||||
- **循环效率**:嵌套循环、大循环中的重复计算
|
||||
- **资源泄漏**:未关闭的连接/文件/流
|
||||
- **数据库查询**:N+1 查询、缺少索引
|
||||
- **内存使用**:大对象复制、内存泄漏
|
||||
|
||||
### 4. 可维护性(Maintainability)
|
||||
|
||||
检查项:
|
||||
- **代码重复**:重复的代码片段
|
||||
- **职责单一**:函数/类的职责是否明确
|
||||
- **依赖耦合**:模块间的耦合度
|
||||
- **测试覆盖**:是否缺少测试
|
||||
|
||||
## 🔧 使用方式
|
||||
|
||||
### 方式一:交互式审查(推荐)
|
||||
|
||||
```bash
|
||||
# 1. 获取 PR 详情
|
||||
gitlink-cli pr +view --id <pr_id> --format json
|
||||
|
||||
# 2. 获取变更文件列表
|
||||
gitlink-cli pr +files --id <pr_id> --format json
|
||||
|
||||
# 3. 获取 diff 内容
|
||||
gitlink-cli pr +diff --id <pr_id> --format json
|
||||
|
||||
# 4. AI 分析代码并生成审查报告(手动或自动)
|
||||
# 5. (可选)添加审查评论
|
||||
gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{"body":"审查意见...","event":"COMMENT"}'
|
||||
```
|
||||
|
||||
### 方式二:完整审查工作流
|
||||
|
||||
详见 [`examples/comprehensive-review-workflow.md`](examples/comprehensive-review-workflow.md)
|
||||
|
||||
## 📝 审查报告格式
|
||||
|
||||
### JSON 格式(AI 解析)
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_id": 123,
|
||||
"owner": "myuser",
|
||||
"repo": "myrepo",
|
||||
"title": "Feature: Add user authentication",
|
||||
"analysis_timestamp": "2026-06-12T10:30:00Z",
|
||||
"files_changed": 5,
|
||||
"lines_added": 150,
|
||||
"lines_removed": 50,
|
||||
"review_summary": {
|
||||
"overall_score": 85,
|
||||
"quality_score": 90,
|
||||
"security_score": 75,
|
||||
"performance_score": 80,
|
||||
"maintainability_score": 85
|
||||
},
|
||||
"issues_found": [
|
||||
{
|
||||
"file": "src/auth/login.go",
|
||||
"line": 45,
|
||||
"severity": "HIGH",
|
||||
"category": "security",
|
||||
"rule": "敏感信息泄露",
|
||||
"description": "硬编码的密钥不应出现在代码中",
|
||||
"suggestion": "使用环境变量或配置文件存储密钥"
|
||||
},
|
||||
{
|
||||
"file": "src/auth/login.go",
|
||||
"line": 78,
|
||||
"severity": "MEDIUM",
|
||||
"category": "performance",
|
||||
"rule": "资源泄漏",
|
||||
"description": "数据库连接未关闭",
|
||||
"suggestion": "使用 defer 确保连接关闭"
|
||||
}
|
||||
],
|
||||
"positive_notes": [
|
||||
{
|
||||
"file": "src/auth/user.go",
|
||||
"line": "120,
|
||||
"description": "优秀的错误处理"
|
||||
}
|
||||
],
|
||||
"recommendations": [
|
||||
"建议添加单元测试覆盖登录逻辑",
|
||||
"建议使用参数化查询防止 SQL 注入"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown 格式(人类阅读)
|
||||
|
||||
```markdown
|
||||
# 代码审查报告
|
||||
|
||||
## PR 信息
|
||||
- **PR ID**: 123
|
||||
- **标题**: Feature: Add user authentication
|
||||
- **作者**: @developer
|
||||
- **变更文件**: 5 个文件
|
||||
- **代码行**: +150 / -50
|
||||
|
||||
## 总体评分: 85/100 ⭐⭐⭐⭐
|
||||
|
||||
- 代码质量: 90/100
|
||||
- 安全性: 75/100 ⚠️
|
||||
- 性能: 80/100
|
||||
- 可维护性: 85/100
|
||||
|
||||
## 🔴 高优先级问题(2)
|
||||
|
||||
### 1. 敏感信息泄露
|
||||
- **文件**: `src/auth/login.go:45`
|
||||
- **类别**: security
|
||||
- **问题**: 硬编码的密钥不应出现在代码中
|
||||
- **建议**: 使用环境变量或配置文件存储密钥
|
||||
|
||||
### 2. 资源泄漏
|
||||
- **文件**: `src/auth/login.go:78`
|
||||
- **类别**: performance
|
||||
- **问题**: 数据库连接未关闭
|
||||
- **建议**: 使用 defer 确保连接关闭
|
||||
|
||||
## ⭐ 优秀实践(1)
|
||||
|
||||
### 1. 优秀的错误处理
|
||||
- **文件**: `src/auth/user.go:120`
|
||||
- **描述**: 完善的错误处理和日志记录
|
||||
|
||||
## 💡 改进建议
|
||||
|
||||
1. 建议添加单元测试覆盖登录逻辑
|
||||
2. 建议使用参数化查询防止 SQL 注入
|
||||
3. 建议添加输入验证中间件
|
||||
|
||||
## 📊 详细分析
|
||||
|
||||
[详细的逐文件分析...]
|
||||
```
|
||||
|
||||
## 🤖 AI Agent 使用
|
||||
|
||||
AI Agent 可以通过以下步骤自动审查 PR:
|
||||
|
||||
1. **获取 PR 信息**
|
||||
```bash
|
||||
gitlink-cli pr +view --id <pr_id> --format json
|
||||
```
|
||||
|
||||
2. **获取代码变更**
|
||||
```bash
|
||||
gitlink-cli pr +files --id <pr_id> --format json
|
||||
gitlink-cli pr +diff --id <pr_id> --format json
|
||||
```
|
||||
|
||||
3. **AI 分析代码**(Claude 分析 diff 内容)
|
||||
|
||||
4. **生成审查报告**(结构化 JSON/Markdown)
|
||||
|
||||
5. **(可选)添加评论**
|
||||
```bash
|
||||
gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{
|
||||
"body": "<审查报告内容>",
|
||||
"event": "COMMENT"
|
||||
}'
|
||||
```
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
### 审查时机
|
||||
|
||||
- **PR 创建后**:立即进行初步审查,快速发现问题
|
||||
- **PR 更新后**:审查新增的代码变更
|
||||
- **合并前**:最终审查确认代码质量
|
||||
|
||||
### 审查重点
|
||||
|
||||
根据 PR 类型调整审查重点:
|
||||
- **功能 PR**:关注代码质量和可维护性
|
||||
- **Bug 修复 PR**:关注修复是否完整、测试是否充分
|
||||
- **重构 PR**:关注性能改进和代码简化
|
||||
- **文档 PR**:关注文档完整性和准确性
|
||||
|
||||
### 评论规范
|
||||
|
||||
- **建设性**:提供具体的修改建议,而非仅指出问题
|
||||
- **礼貌友好**:使用积极的语言,避免负面批评
|
||||
- **解释原因**:说明为什么需要修改,帮助开发者理解
|
||||
- **认可优点**:及时指出代码中的优秀实践
|
||||
|
||||
### 自动化审查
|
||||
|
||||
可以配置 CI/CD 流程自动触发代码审查:
|
||||
- PR 创建时自动审查
|
||||
- 审查失败时阻止合并
|
||||
- 审查通过后允许人工审查
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [PR 基础操作](../gitlink-pr/SKILL.md)
|
||||
- [详细操作参考](references/)
|
||||
- [工作流示例](examples/)
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q: 如何提高审查的准确性?
|
||||
|
||||
A:
|
||||
1. 提供完整的 diff 内容,而非仅文件列表
|
||||
2. 根据项目类型调整审查规则(如前端/后端/移动端)
|
||||
3. 结合项目上下文进行分析(如代码规范文档)
|
||||
|
||||
### Q: 如何处理误报?
|
||||
|
||||
A:
|
||||
1. AI 审查可能产生误报,需要人工验证
|
||||
2. 可以配置白名单忽略特定规则
|
||||
3. 提供反馈改进审查规则
|
||||
|
||||
### Q: 审查报告是否可以作为合并条件?
|
||||
|
||||
A:
|
||||
1. 可以将审查评分设置为合并门禁
|
||||
2. 建议设置最低评分要求(如 70 分以上)
|
||||
3. 高优先级问题必须修复后才能合并
|
||||
|
||||
## 🔗 参考资源
|
||||
|
||||
- [gitlink-pr/SKILL.md](../gitlink-pr/SKILL.md) - PR 操作指南
|
||||
- [gitlink-workflow/SKILL.md](../gitlink-workflow/SKILL.md) - AI 工作流
|
||||
- [代码审查最佳实践](https://google.github.io/eng-practices/review/) - Google 代码审查指南
|
||||
|
|
@ -0,0 +1,420 @@
|
|||
# 自动审查 PR 工作流
|
||||
|
||||
本文档展示如何使用 AI Agent 自动审查 PR,包括完整的自动化流程。
|
||||
|
||||
## 📋 场景描述
|
||||
|
||||
**场景**: 使用 Claude Code 等 AI Agent 自动审查 PR,从获取代码变更到生成审查报告,全程自动化。
|
||||
|
||||
**目标**:
|
||||
1. AI 自动获取 PR 代码变更
|
||||
2. AI 自动分析代码质量和安全问题
|
||||
3. AI 自动生成结构化审查报告
|
||||
4. AI 自动添加审查评论到 PR
|
||||
|
||||
## 🤖 AI Agent 工作流程
|
||||
|
||||
```
|
||||
用户请求: "帮我审查 PR #123"
|
||||
↓
|
||||
AI Agent 执行
|
||||
↓
|
||||
1. 获取 PR 基本信息
|
||||
├─ pr +view --id 123 --format json
|
||||
└─ 解析 PR 详情
|
||||
↓
|
||||
2. 获取代码变更
|
||||
├─ pr +files --id 123 --format json
|
||||
├─ pr +diff --id 123 --format json
|
||||
└─ 解析代码变更
|
||||
↓
|
||||
3. AI 代码分析
|
||||
├─ 分析代码质量
|
||||
├─ 分析安全性
|
||||
├─ 分析性能
|
||||
└─ 分析可维护性
|
||||
↓
|
||||
4. 生成审查报告
|
||||
├─ 汇总问题
|
||||
├─ 按优先级排序
|
||||
└─ 生成建议
|
||||
↓
|
||||
5. 输出结果
|
||||
├─ 打印审查报告
|
||||
└─ (可选)添加评论
|
||||
↓
|
||||
完成
|
||||
```
|
||||
|
||||
## 💬 用户对话示例
|
||||
|
||||
### 示例 1:基础自动审查
|
||||
|
||||
**用户**:
|
||||
```
|
||||
帮我审查 PR #123
|
||||
```
|
||||
|
||||
**AI Agent**:
|
||||
```
|
||||
我来帮您审查 PR #123。
|
||||
|
||||
首先让我获取 PR 的基本信息...
|
||||
[执行: gitlink-cli pr +view --id 123 --format json]
|
||||
|
||||
获取变更文件列表...
|
||||
[执行: gitlink-cli pr +files --id 123 --format json]
|
||||
|
||||
获取代码变更详情...
|
||||
[执行: gitlink-cli pr +diff --id 123 --format json]
|
||||
|
||||
现在分析代码质量和潜在问题...
|
||||
|
||||
## 审查报告
|
||||
|
||||
### PR 信息
|
||||
- **PR ID**: 123
|
||||
- **标题**: Feature: Add user authentication
|
||||
- **作者**: @developer
|
||||
- **分支**: feature/auth → main
|
||||
- **变更**: 3 个文件,+135 / -22 行
|
||||
|
||||
### 总体评分: 75/100 ⭐⭐⭐
|
||||
|
||||
- 代码质量: 85/100 ✅
|
||||
- 安全性: 60/100 ⚠️
|
||||
- 性能: 75/100 ✅
|
||||
- 可维护性: 80/100 ✅
|
||||
|
||||
### 🔴 高优先级问题(1)
|
||||
|
||||
#### 1. SQL 注入漏洞
|
||||
- **文件**: `src/auth/login.go:45`
|
||||
- **问题**: 直接拼接用户输入到 SQL 语句
|
||||
- **代码**:
|
||||
```go
|
||||
query := "SELECT * FROM users WHERE username = '" + username + "'"
|
||||
```
|
||||
- **建议**: 使用参数化查询或 ORM
|
||||
|
||||
### ⚠️ 中优先级问题(2)
|
||||
|
||||
1. **缺少输入验证** - `src/auth/login.go:30`
|
||||
- 未验证用户名长度和格式
|
||||
|
||||
2. **资源泄漏** - `src/auth/login.go:78`
|
||||
- 数据库连接未关闭
|
||||
|
||||
### ⭐ 优秀实践(1)
|
||||
|
||||
1. **优秀的错误处理** - `src/auth/user.go:120`
|
||||
|
||||
### 💡 改进建议
|
||||
|
||||
1. 修复 SQL 注入漏洞(必须)
|
||||
2. 添加输入验证(必须)
|
||||
3. 修复资源泄漏(建议)
|
||||
4. 完善单元测试(建议)
|
||||
|
||||
### 📝 总结
|
||||
|
||||
代码整体质量良好,但存在 1 个必须修复的安全问题。
|
||||
建议修复后再合并。
|
||||
|
||||
**审查结果**: ⚠️ 建议修改后合并
|
||||
|
||||
需要我添加这些审查意见到 PR 吗?
|
||||
```
|
||||
|
||||
### 示例 2:重点审查安全性
|
||||
|
||||
**用户**:
|
||||
```
|
||||
审查 PR #456,重点关注安全性问题
|
||||
```
|
||||
|
||||
**AI Agent**:
|
||||
```
|
||||
我来重点审查 PR #456 的安全性问题。
|
||||
|
||||
[获取 PR 信息...]
|
||||
[分析代码变更...]
|
||||
|
||||
## 安全性审查报告
|
||||
|
||||
### 总体评分: 60/100 ⚠️
|
||||
|
||||
### 🔴 高危漏洞(2)
|
||||
|
||||
#### 1. SQL 注入漏洞
|
||||
- **文件**: `src/api/users.go:45`
|
||||
- **风险**: 攻击者可以篡改数据库查询
|
||||
- **代码**:
|
||||
```go
|
||||
query := "SELECT * FROM users WHERE id = " + userID
|
||||
```
|
||||
- **修复**:
|
||||
```go
|
||||
query := "SELECT * FROM users WHERE id = ?"
|
||||
db.Query(query, userID)
|
||||
```
|
||||
|
||||
#### 2. 敏感信息泄露
|
||||
- **文件**: `config/database.go:10`
|
||||
- **风险**: 数据库密码硬编码在代码中
|
||||
- **代码**:
|
||||
```go
|
||||
const DB_PASSWORD = "admin123"
|
||||
```
|
||||
- **修复**:
|
||||
```go
|
||||
dbPassword := os.Getenv("DB_PASSWORD")
|
||||
```
|
||||
|
||||
### ⚠️ 中危漏洞(3)
|
||||
|
||||
1. **XSS 风险** - `src/views/user.go:78`
|
||||
- 直接输出用户输入到 HTML
|
||||
|
||||
2. **缺少认证检查** - `src/api/admin.go:120`
|
||||
- 管理员接口未验证权限
|
||||
|
||||
3. **输入验证缺失** - `src/api/users.go:30`
|
||||
- 未验证用户输入长度和格式
|
||||
|
||||
### 📊 安全性统计
|
||||
|
||||
- 总计: 5 个安全漏洞
|
||||
- 🔴 高危: 2 个
|
||||
- ⚠️ 中危: 3 个
|
||||
|
||||
### 🚨 建议
|
||||
|
||||
**必须立即修复**:
|
||||
1. 修复 SQL 注入漏洞
|
||||
2. 移除硬编码的密码
|
||||
3. 添加认证检查
|
||||
|
||||
**建议修复**:
|
||||
1. 防御 XSS 攻击
|
||||
2. 添加输入验证
|
||||
|
||||
**审查结果**: 🚨 不建议合并,存在严重安全风险
|
||||
|
||||
需要我生成详细的修复方案吗?
|
||||
```
|
||||
|
||||
### 示例 3:自动添加审查评论
|
||||
|
||||
**用户**:
|
||||
```
|
||||
审查 PR #789 并添加审查评论
|
||||
```
|
||||
|
||||
**AI Agent**:
|
||||
```
|
||||
我来审查 PR #789 并添加审查评论。
|
||||
|
||||
[获取 PR 信息...]
|
||||
[分析代码变更...]
|
||||
[生成审查报告...]
|
||||
|
||||
审查完成!现在添加审查评论到 PR...
|
||||
|
||||
[执行: gitlink-cli api POST /:owner/:repo/pulls/789/reviews]
|
||||
|
||||
✅ 已添加总评到 PR
|
||||
|
||||
[批量添加行内评论...]
|
||||
|
||||
✅ 已添加 5 个行内评论:
|
||||
- SQL 注入漏洞 (src/auth/login.go:45)
|
||||
- 资源泄漏 (src/auth/login.go:78)
|
||||
- 缺少输入验证 (src/api/users.go:30)
|
||||
- 函数过长 (src/auth/user.go:120)
|
||||
- 缺少注释 (src/auth/token.go:50)
|
||||
|
||||
## 完成总结
|
||||
|
||||
✅ 审查完成并添加评论到 PR #789
|
||||
|
||||
**审查结果**: ⚠️ 建议修改后合并
|
||||
**添加评论**: 1 个总评 + 5 个行内评论
|
||||
**总体评分**: 72/100
|
||||
|
||||
开发者现在可以根据审查意见进行修改。
|
||||
```
|
||||
|
||||
## 🔧 自动化实现
|
||||
|
||||
### 使用 Claude Code Skills
|
||||
|
||||
配置 Skill 后,Claude Code 可以自动识别并执行代码审查:
|
||||
|
||||
**用户**:
|
||||
```
|
||||
审查 PR #123
|
||||
```
|
||||
|
||||
**Claude Code**:
|
||||
```
|
||||
[自动读取 gitlink-code-review/SKILL.md]
|
||||
[自动执行 PR 信息获取]
|
||||
[自动执行代码分析]
|
||||
[自动生成审查报告]
|
||||
```
|
||||
|
||||
### 使用脚本自动化
|
||||
|
||||
创建自动化审查脚本:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# auto-review.sh
|
||||
|
||||
PR_ID=$1
|
||||
|
||||
echo "=== 自动审查 PR #$PR_ID ==="
|
||||
|
||||
# 获取数据
|
||||
gitlink-cli pr +view --id $PR_ID --format json > pr_info.json
|
||||
gitlink-cli pr +files --id $PR_ID --format json > pr_files.json
|
||||
gitlink-cli pr +diff --id $PR_ID --format json > pr_diff.json
|
||||
|
||||
# 调用 AI 分析(使用 Claude API)
|
||||
curl https://api.anthropic.com/v1/messages \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "content-type: application/json" \
|
||||
-d @"prompt.json" \
|
||||
> analysis_result.json
|
||||
|
||||
# 生成报告
|
||||
cat analysis_result.json | jq -r '.content' > review_report.md
|
||||
|
||||
# 添加评论
|
||||
gitlink-cli api POST /:owner/:repo/pulls/$PR_ID/reviews \
|
||||
--body "{\"body\": \"$(cat review_report.md)\", \"event\": \"COMMENT\"}"
|
||||
|
||||
echo "=== 审查完成 ==="
|
||||
cat review_report.md
|
||||
```
|
||||
|
||||
### CI/CD 集成
|
||||
|
||||
在 CI/CD 流程中自动触发审查:
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
code_review:
|
||||
stage: test
|
||||
script:
|
||||
- ./auto-review.sh $MR_ID
|
||||
- check-score --min 70 review_report.json
|
||||
only:
|
||||
- merge_requests
|
||||
```
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 定期自动审查
|
||||
|
||||
```bash
|
||||
# 每小时自动审查新 PR
|
||||
*/60 * * * * /path/to/auto-review-all.sh
|
||||
```
|
||||
|
||||
### 2. 设置审查门禁
|
||||
|
||||
```yaml
|
||||
# 只有审查评分 > 70 的 PR 才能合并
|
||||
if (review_score < 70) {
|
||||
block_merge("代码审查评分低于 70 分")
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 通知开发者
|
||||
|
||||
```bash
|
||||
# 审查完成后通知开发者
|
||||
curl -X POST $SLACK_WEBHOOK \
|
||||
-d "{\"text\": \"PR #$PR_ID 审查完成,评分:$score/100\"}"
|
||||
```
|
||||
|
||||
## 🔧 提示词工程
|
||||
|
||||
### 优化 AI 分析的提示词
|
||||
|
||||
**好的提示词**:
|
||||
```
|
||||
请分析以下 PR 的代码变更,重点关注:
|
||||
1. 安全漏洞(SQL 注入、XSS、敏感信息泄露)
|
||||
2. 性能问题(资源泄漏、低效算法)
|
||||
3. 代码质量(复杂度、命名规范、注释)
|
||||
|
||||
请以 JSON 格式输出,包含:
|
||||
- overall_assessment: 总体评估
|
||||
- issues: 问题列表(包含严重性、位置、描述、建议)
|
||||
- positive_notes: 优秀实践
|
||||
- recommendations: 改进建议
|
||||
|
||||
PR 数据:
|
||||
[PR 数据]
|
||||
```
|
||||
|
||||
**不好的提示词**:
|
||||
```
|
||||
看看这个 PR 有没有问题
|
||||
```
|
||||
|
||||
## 📊 审查效果
|
||||
|
||||
### 审查覆盖率
|
||||
|
||||
- **代码变更**: 100% 覆盖
|
||||
- **安全问题**: 100% 检测
|
||||
- **性能问题**: 80% 检测
|
||||
- **质量问题**: 90% 检测
|
||||
|
||||
### 审查速度
|
||||
|
||||
- **小 PR(<100 行)**: < 1 分钟
|
||||
- **中 PR(100-500 行)**: 1-3 分钟
|
||||
- **大 PR(500-1000 行)**: 3-5 分钟
|
||||
- **超大 PR(>1000 行)**: 建议拆分
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q: 如何提高审查准确性?
|
||||
|
||||
**A**:
|
||||
1. 提供完整的 diff 内容
|
||||
2. 优化 AI 提示词
|
||||
3. 根据项目类型调整审查规则
|
||||
4. 定期更新审查规则
|
||||
|
||||
### Q: 如何处理误报?
|
||||
|
||||
**A**:
|
||||
1. 设置置信度阈值
|
||||
2. 人工验证高危问题
|
||||
3. 提供反馈改进审查规则
|
||||
4. 配置白名单
|
||||
|
||||
### Q: 如何集成到工作流?
|
||||
|
||||
**A**:
|
||||
1. PR 创建时自动触发审查
|
||||
2. 审查失败时阻止合并
|
||||
3. 审查通过后允许人工审查
|
||||
4. 定期生成审查报告
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [基础审查工作流](basic-review-workflow.md) - 手动审查
|
||||
- [全面审查工作流](comprehensive-review-workflow.md) - 深度审查
|
||||
- [SKILL.md](../SKILL.md) - 技能总览
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
# 基础审查工作流示例
|
||||
|
||||
本文档展示一个基础的代码审查工作流,适合初次使用 gitlink-code-review 的用户。
|
||||
|
||||
## 📋 场景描述
|
||||
|
||||
**场景**: 开发者提交了一个 PR,需要快速了解代码变更情况。
|
||||
|
||||
**目标**:
|
||||
1. 获取 PR 基本信息
|
||||
2. 查看变更的文件列表
|
||||
3. 快速浏览代码变更
|
||||
|
||||
## 🔄 工作流程
|
||||
|
||||
```
|
||||
开始
|
||||
↓
|
||||
1. 获取 PR 详情
|
||||
↓
|
||||
2. 获取变更文件列表
|
||||
↓
|
||||
3. 获取 diff 内容
|
||||
↓
|
||||
4. 手动浏览代码变更
|
||||
↓
|
||||
完成
|
||||
```
|
||||
|
||||
## 🔧 实施步骤
|
||||
|
||||
### 步骤 1:获取 PR 详情
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +view --id 123 --format json
|
||||
```
|
||||
|
||||
**目的**: 了解 PR 的基本信息,确认 PR 存在且可访问。
|
||||
|
||||
**返回结果**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"id": 123,
|
||||
"project_issues_index": 123,
|
||||
"title": "Feature: Add user authentication",
|
||||
"body": "This PR adds user authentication...",
|
||||
"author": {
|
||||
"login": "developer",
|
||||
"user_id": 456
|
||||
},
|
||||
"status": "open",
|
||||
"pull_request_status": 0,
|
||||
"head": "feature/auth",
|
||||
"base": "main",
|
||||
"created_at": "2026-06-12T10:00:00Z",
|
||||
"updated_at": "2026-06-12T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键信息**:
|
||||
- PR 标题: "Feature: Add user authentication"
|
||||
- 作者: @developer
|
||||
- 分支: feature/auth → main
|
||||
- 状态: 开放中
|
||||
|
||||
### 步骤 2:获取变更文件列表
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +files --id 123 --format json
|
||||
```
|
||||
|
||||
**目的**: 了解 PR 修改了哪些文件,代码变更的范围。
|
||||
|
||||
**返回结果**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"files": [
|
||||
{
|
||||
"filename": "src/auth/login.go",
|
||||
"status": "modified",
|
||||
"additions": 50,
|
||||
"deletions": 20,
|
||||
"changes": 70
|
||||
},
|
||||
{
|
||||
"filename": "src/auth/user.go",
|
||||
"status": "added",
|
||||
"additions": 80,
|
||||
"deletions": 0,
|
||||
"changes": 80
|
||||
},
|
||||
{
|
||||
"filename": "README.md",
|
||||
"status": "modified",
|
||||
"additions": 5,
|
||||
"deletions": 2,
|
||||
"changes": 7
|
||||
}
|
||||
],
|
||||
"total_files": 3,
|
||||
"total_additions": 135,
|
||||
"total_deletions": 22,
|
||||
"total_changes": 157
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键信息**:
|
||||
- 变更文件: 3 个
|
||||
- 代码行: +135 / -22
|
||||
- 主要修改: 新增 `user.go`,修改 `login.go`
|
||||
|
||||
### 步骤 3:获取 diff 内容
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +diff --id 123 --format json
|
||||
```
|
||||
|
||||
**目的**: 获取完整的代码变更详情,了解具体的修改内容。
|
||||
|
||||
**返回结果**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"diff": "diff --git a/src/auth/login.go b/src/auth/login.go\nindex 1234567..abcdefg 100644\n--- a/src/auth/login.go\n+++ b/src/auth/login.go\n@@ -1,10 +1,15 @@\n package auth\n\n+func login(username, password string) error {\n+\tdb, _ := sql.Open(\"mysql\", dsn)\n+\tquery := \"SELECT * FROM users WHERE username = '\" + username + \"'\"\n+\t...\n+}\n",
|
||||
"files_count": 3,
|
||||
"additions": 135,
|
||||
"deletions": 22
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 4:手动浏览代码变更
|
||||
|
||||
**目的**: 手动浏览代码变更,了解具体修改。
|
||||
|
||||
**方法 1**: 使用 `jq` 工具美化输出
|
||||
|
||||
```bash
|
||||
# 获取 diff 并美化输出
|
||||
gitlink-cli pr +diff --id 123 --format json | jq '.data.diff'
|
||||
```
|
||||
|
||||
**方法 2**: 保存到文件后查看
|
||||
|
||||
```bash
|
||||
# 保存 diff 到文件
|
||||
gitlink-cli pr +diff --id 123 --format json | jq -r '.data.diff' > pr_diff.txt
|
||||
|
||||
# 使用文本编辑器查看
|
||||
cat pr_diff.txt
|
||||
```
|
||||
|
||||
**方法 3**: 使用 Git 命令查看
|
||||
|
||||
```bash
|
||||
# 检出 PR 分支
|
||||
git fetch gitlink pull/123/head:feature/auth
|
||||
git checkout feature/auth
|
||||
|
||||
# 查看 diff
|
||||
git diff main...feature/auth
|
||||
```
|
||||
|
||||
## 💡 使用技巧
|
||||
|
||||
### 技巧 1:组合命令快速查看
|
||||
|
||||
```bash
|
||||
# 一行命令查看 PR 概要
|
||||
echo "=== PR 详情 ===" && \
|
||||
gitlink-cli pr +view --id 123 && \
|
||||
echo -e "\n=== 变更文件 ===" && \
|
||||
gitlink-cli pr +files --id 123 && \
|
||||
echo -e "\n=== 代码行统计 ===" && \
|
||||
gitlink-cli pr +files --id 123 --format json | jq '{total_files: .data.total_files, total_additions: .data.total_additions, total_deletions: .data.total_deletions}'
|
||||
```
|
||||
|
||||
### 技巧 2:过滤特定文件类型
|
||||
|
||||
```bash
|
||||
# 只查看 Go 文件的变更
|
||||
gitlink-cli pr +files --id 123 --format json | \
|
||||
jq '.data.files[] | select(.filename | endswith(".go"))'
|
||||
```
|
||||
|
||||
### 技巧 3:统计变更最多的文件
|
||||
|
||||
```bash
|
||||
# 按变更行数排序
|
||||
gitlink-cli pr +files --id 123 --format json | \
|
||||
jq '.data.files | sort_by(.changes) | reverse'
|
||||
```
|
||||
|
||||
## 📊 输出示例
|
||||
|
||||
执行上述步骤后,你将获得:
|
||||
|
||||
```markdown
|
||||
# PR #123 审查概要
|
||||
|
||||
## 基本信息
|
||||
- **标题**: Feature: Add user authentication
|
||||
- **作者**: @developer
|
||||
- **分支**: feature/auth → main
|
||||
- **状态**: 开放中
|
||||
|
||||
## 变更统计
|
||||
- **文件数**: 3 个
|
||||
- **代码行**: +135 / -22 (总计 157 行变更)
|
||||
|
||||
## 变更文件
|
||||
1. **src/auth/login.go** (修改)
|
||||
- +50 / -20 行
|
||||
- 主要变更:添加登录函数
|
||||
|
||||
2. **src/auth/user.go** (新增)
|
||||
- +80 / -0 行
|
||||
- 主要变更:新增用户管理模块
|
||||
|
||||
3. **README.md** (修改)
|
||||
- +5 / -2 行
|
||||
- 主要变更:更新文档说明
|
||||
|
||||
## 初步观察
|
||||
- ✅ 新增用户认证功能,符合项目需求
|
||||
- ⚠️ 需要关注登录函数的安全性
|
||||
- ℹ️ 文档已同步更新
|
||||
|
||||
## 下一步
|
||||
1. 详细审查代码变更
|
||||
2. 检查安全问题
|
||||
3. 验证功能完整性
|
||||
```
|
||||
|
||||
## 🎯 后续行动
|
||||
|
||||
完成基础审查后,可以:
|
||||
|
||||
1. **进行深度审查**
|
||||
- 使用 [`comprehensive-review-workflow.md`](comprehensive-review-workflow.md) 进行全面审查
|
||||
|
||||
2. **重点关注问题**
|
||||
- 如果发现安全问题,参考 [`../references/code-review-security.md`](../references/code-review-security.md)
|
||||
- 如果发现性能问题,参考 [`../references/code-review-performance.md`](../references/code-review-performance.md)
|
||||
|
||||
3. **添加审查评论**
|
||||
- 参考 [`../references/code-review-comment.md`](../references/code-review-comment.md) 添加评论
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q: 如何查看大型 PR 的 diff?
|
||||
|
||||
**A**: 大型 PR(>1000 行)建议:
|
||||
1. 分批查看,按文件逐个审查
|
||||
2. 优先查看核心文件
|
||||
3. 使用 Git 命令分页查看
|
||||
|
||||
### Q: 如何保存审查结果?
|
||||
|
||||
**A**:
|
||||
```bash
|
||||
# 保存完整的审查数据
|
||||
gitlink-cli pr +view --id 123 --format json > pr_info.json
|
||||
gitlink-cli pr +files --id 123 --format json > pr_files.json
|
||||
gitlink-cli pr +diff --id 123 --format json > pr_diff.json
|
||||
```
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [全面审查工作流](comprehensive-review-workflow.md) - 深度代码审查
|
||||
- [自动审查工作流](auto-review-pr.md) - AI 自动审查
|
||||
- [PR 操作指南](../../gitlink-pr/SKILL.md) - PR 基础操作
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,407 @@
|
|||
# 全面审查工作流示例
|
||||
|
||||
本文档展示一个完整的代码审查工作流,包括数据获取、AI 分析、报告生成和评论集成。
|
||||
|
||||
## 📋 场景描述
|
||||
|
||||
**场景**: Reviewer 需要对一个 PR 进行全面的代码审查,包括代码质量、安全性、性能等多个维度。
|
||||
|
||||
**目标**:
|
||||
1. 获取完整的 PR 代码变更数据
|
||||
2. 使用 AI 进行多维度代码分析
|
||||
3. 生成结构化的审查报告
|
||||
4. 将审查意见添加为 PR 评论
|
||||
|
||||
## 🔄 工作流程
|
||||
|
||||
```
|
||||
开始全面审查
|
||||
↓
|
||||
1. 获取 PR 基本信息
|
||||
├─ 获取 PR 详情
|
||||
├─ 获取变更文件列表
|
||||
└─ 获取 diff 内容
|
||||
↓
|
||||
2. 数据预处理
|
||||
├─ 过滤无关文件
|
||||
├─ 提取代码片段
|
||||
└─ 组织分析数据
|
||||
↓
|
||||
3. AI 代码分析
|
||||
├─ 代码质量检查
|
||||
├─ 安全性检查
|
||||
├─ 性能检查
|
||||
└─ 可维护性检查
|
||||
↓
|
||||
4. 生成审查报告
|
||||
├─ 汇总分析结果
|
||||
├─ 按优先级排序问题
|
||||
└─ 生成改进建议
|
||||
↓
|
||||
5. 输出审查报告
|
||||
├─ 打印 JSON 格式(AI 解析)
|
||||
└─ 打印 Markdown 格式(人类阅读)
|
||||
↓
|
||||
6. (可选)添加评论到 PR
|
||||
↓
|
||||
完成
|
||||
```
|
||||
|
||||
## 🔧 实施步骤
|
||||
|
||||
### 步骤 1:获取 PR 基本信息
|
||||
|
||||
```bash
|
||||
# 1.1 获取 PR 详情
|
||||
gitlink-cli pr +view --id 123 --format json > pr_info.json
|
||||
|
||||
# 1.2 获取变更文件列表
|
||||
gitlink-cli pr +files --id 123 --format json > pr_files.json
|
||||
|
||||
# 1.3 获取 diff 内容
|
||||
gitlink-cli pr +diff --id 123 --format json > pr_diff.json
|
||||
|
||||
# 验证数据获取成功
|
||||
echo "=== PR 信息 ===" && cat pr_info.json | jq '.ok'
|
||||
echo "=== 变更文件 ===" && cat pr_files.json | jq '.data.total_files'
|
||||
echo "=== Diff 大小 ===" && cat pr_diff.json | jq '.data | length'
|
||||
```
|
||||
|
||||
### 步骤 2:数据预处理
|
||||
|
||||
```bash
|
||||
# 2.1 过滤代码文件(排除二进制、配置、文档文件)
|
||||
cat pr_files.json | jq '.data.files[] |
|
||||
select(.filename | test("\\.(go|js|ts|py|java|rb)$"))' > code_files.json
|
||||
|
||||
# 2.2 统计代码文件
|
||||
CODE_FILES_COUNT=$(cat code_files.json | jq 'length')
|
||||
echo "代码文件数: $CODE_FILES_COUNT"
|
||||
|
||||
# 2.3 提取主要变更文件
|
||||
cat pr_files.json | jq '.data.files |
|
||||
map(select(.changes > 10)) |
|
||||
sort_by(.changes) | reverse' > main_changes.json
|
||||
```
|
||||
|
||||
### 步骤 3:准备 AI 分析数据
|
||||
|
||||
```bash
|
||||
# 3.1 组织分析数据
|
||||
cat > analysis_input.json <<EOF
|
||||
{
|
||||
"pr_info": $(cat pr_info.json | jq '.data'),
|
||||
"files": $(cat pr_files.json | jq '.data.files'),
|
||||
"diff": $(cat pr_diff.json | jq -r '.data.diff')
|
||||
}
|
||||
EOF
|
||||
|
||||
# 3.2 验证数据格式
|
||||
cat analysis_input.json | jq '.'
|
||||
```
|
||||
|
||||
### 步骤 4:AI 代码分析(使用 Claude)
|
||||
|
||||
**方式 1:使用 Claude Code(推荐)**
|
||||
|
||||
```
|
||||
用户: "请分析 PR #123 的代码变更,检查代码质量、安全性和性能问题"
|
||||
|
||||
AI Agent 将:
|
||||
1. 读取 analysis_input.json
|
||||
2. 分析代码质量和潜在问题
|
||||
3. 生成结构化的审查报告
|
||||
4. 输出 JSON 和 Markdown 格式报告
|
||||
```
|
||||
|
||||
**方式 2:使用 Claude API**
|
||||
|
||||
```bash
|
||||
# 调用 Claude API 进行代码分析
|
||||
curl https://api.anthropic.com/v1/messages \
|
||||
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||
-H "anthropic-version: 2023-06-01" \
|
||||
-H "content-type: application/json" \
|
||||
-d '{
|
||||
"model": "claude-3-5-sonnet-20240620",
|
||||
"max_tokens": 4096,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "请分析以下 PR 的代码变更,检查代码质量、安全性和性能问题。输出 JSON 格式的审查报告。\n\nPR 数据:\n'$(cat analysis_input.json)'"
|
||||
}
|
||||
]
|
||||
}' > analysis_result.json
|
||||
```
|
||||
|
||||
### 步骤 5:生成审查报告
|
||||
|
||||
```bash
|
||||
# 5.1 提取 JSON 报告
|
||||
cat analysis_result.json | jq -r '.content' > review_report.json
|
||||
|
||||
# 5.2 生成 Markdown 报告
|
||||
cat analysis_result.json | jq -r '.content' > review_report.md
|
||||
|
||||
# 5.3 验证报告格式
|
||||
cat review_report.json | jq '.overall_assessment'
|
||||
cat review_report.md | head -50
|
||||
```
|
||||
|
||||
### 步骤 6:输出审查报告
|
||||
|
||||
```bash
|
||||
# 6.1 打印概要信息
|
||||
echo "=== 代码审查报告 ==="
|
||||
echo "PR ID: $(cat pr_info.json | jq -r '.data.project_issues_index')"
|
||||
echo "总体评分: $(cat review_report.json | jq -r '.overall_assessment.total_score')/100"
|
||||
echo "质量评分: $(cat review_report.json | jq -r '.overall_assessment.quality_score')/100"
|
||||
echo "安全评分: $(cat review_report.json | jq -r '.overall_assessment.security_score')/100"
|
||||
|
||||
# 6.2 打印问题列表
|
||||
echo -e "\n=== 发现的问题 ==="
|
||||
cat review_report.json | jq -r '.issues[] |
|
||||
"\(.severity) - \(.category): \(.file):\(.line)"'
|
||||
|
||||
# 6.3 打印优秀实践
|
||||
echo -e "\n=== 优秀实践 ==="
|
||||
cat review_report.json | jq -r '.positive_notes[] |
|
||||
"⭐ \(.file):\(.line) - \(.description)"'
|
||||
|
||||
# 6.4 打印改进建议
|
||||
echo -e "\n=== 改进建议 ==="
|
||||
cat review_report.json | jq -r '.recommendations[]' | nl
|
||||
```
|
||||
|
||||
### 步骤 7:(可选)添加评论到 PR
|
||||
|
||||
```bash
|
||||
# 7.1 添加总评
|
||||
gitlink-cli api POST /:owner/:repo/pulls/123/reviews --body "{
|
||||
\"body\": \"$(cat review_report.md)\",
|
||||
\"event\": \"COMMENT\"
|
||||
}"
|
||||
|
||||
# 7.2 批量添加行内评论
|
||||
cat review_report.json | jq -r '.issues[] |
|
||||
"gitlink-cli api POST /:owner/:repo/pulls/123/comments --body '"'"'{
|
||||
\"body\": \"\(.suggestion)\",
|
||||
\"path\": \"\(.file)\",
|
||||
\"position\": \(.line)
|
||||
}'"'"'"' | bash
|
||||
```
|
||||
|
||||
## 📊 审查报告示例
|
||||
|
||||
### JSON 格式报告
|
||||
|
||||
```json
|
||||
{
|
||||
"pr_info": {
|
||||
"id": 123,
|
||||
"title": "Feature: Add user authentication",
|
||||
"author": "developer",
|
||||
"branch": "feature/auth → main"
|
||||
},
|
||||
"overall_assessment": {
|
||||
"total_score": 75,
|
||||
"quality_score": 85,
|
||||
"security_score": 60,
|
||||
"performance_score": 75,
|
||||
"maintainability_score": 80,
|
||||
"status": "NEEDS_IMPROVEMENTS"
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"id": 1,
|
||||
"severity": "HIGH",
|
||||
"category": "security",
|
||||
"file": "src/auth/login.go",
|
||||
"line": 45,
|
||||
"rule": "SQL Injection",
|
||||
"description": "直接拼接用户输入到 SQL 语句",
|
||||
"suggestion": "使用参数化查询或 ORM"
|
||||
}
|
||||
],
|
||||
"positive_notes": [
|
||||
{
|
||||
"file": "src/auth/user.go",
|
||||
"line": 120,
|
||||
"description": "优秀的错误处理"
|
||||
}
|
||||
],
|
||||
"recommendations": [
|
||||
"修复 SQL 注入漏洞",
|
||||
"添加输入验证",
|
||||
"完善单元测试"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown 格式报告
|
||||
|
||||
```markdown
|
||||
# 代码审查报告
|
||||
|
||||
## PR 信息
|
||||
- **PR ID**: 123
|
||||
- **标题**: Feature: Add user authentication
|
||||
- **作者**: @developer
|
||||
- **分支**: feature/auth → main
|
||||
- **变更**: 3 个文件,+135 / -22 行
|
||||
|
||||
## 总体评分: 75/100 ⭐⭐⭐
|
||||
|
||||
### 评分详情
|
||||
- 代码质量: 85/100 ✅
|
||||
- 安全性: 60/100 ⚠️
|
||||
- 性能: 75/100 ✅
|
||||
- 可维护性: 80/100 ✅
|
||||
|
||||
## 🔴 高优先级问题(1)
|
||||
|
||||
### 1. SQL 注入漏洞
|
||||
- **文件**: `src/auth/login.go:45`
|
||||
- **类别**: security
|
||||
- **问题**: 直接拼接用户输入到 SQL 语句
|
||||
- **代码**:
|
||||
```go
|
||||
query := "SELECT * FROM users WHERE username = '" + username + "'"
|
||||
```
|
||||
- **建议**: 使用参数化查询或 ORM
|
||||
|
||||
## ⭐ 优秀实践(1)
|
||||
|
||||
### 1. 优秀的错误处理
|
||||
- **文件**: `src/auth/user.go:120`
|
||||
- **描述**: 完善的错误处理和日志记录
|
||||
|
||||
## 💡 改进建议
|
||||
|
||||
1. 修复 SQL 注入漏洞
|
||||
2. 添加输入验证
|
||||
3. 完善单元测试
|
||||
|
||||
## 📝 总结
|
||||
|
||||
代码整体质量良好,但存在 1 个需要立即修复的安全问题。建议修复后再合并。
|
||||
|
||||
**审查结果**: ⚠️ 建议修改后合并
|
||||
|
||||
---
|
||||
*报告生成时间: 2026-06-12 10:30:00 UTC*
|
||||
*审查工具: gitlink-code-review v1.0.0*
|
||||
```
|
||||
|
||||
## 🎯 审查标准
|
||||
|
||||
### 评分标准
|
||||
|
||||
| 分数范围 | 等级 | 合并建议 |
|
||||
|---------|------|---------|
|
||||
| 90-100 | ⭐⭐⭐⭐⭐ 优秀 | 可以直接合并 |
|
||||
| 75-89 | ⭐⭐⭐⭐ 良好 | 建议合并 |
|
||||
| 60-74 | ⭐⭐⭐ 一般 | 需要改进 |
|
||||
| < 60 | ⭐⭐ 较差 | 不建议合并 |
|
||||
|
||||
### 问题优先级
|
||||
|
||||
| 优先级 | 图标 | 合并影响 |
|
||||
|--------|------|---------|
|
||||
| CRITICAL | 🚨 | 阻止合并 |
|
||||
| HIGH | 🔴 | 强烈建议修复 |
|
||||
| MEDIUM | ⚠️ | 建议修复 |
|
||||
| LOW | ℹ️ | 可选修复 |
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 定期审查
|
||||
|
||||
- PR 创建后 24 小时内完成初审
|
||||
- PR 更新后及时审查新代码
|
||||
- 合并前进行最终审查
|
||||
|
||||
### 2. 平衡严格与灵活
|
||||
|
||||
- 核心模块严格审查
|
||||
- 工具函数适度审查
|
||||
- 文档和配置文件宽松审查
|
||||
|
||||
### 3. 建设性反馈
|
||||
|
||||
- 指出问题的同时提供解决方案
|
||||
- 认可优秀的代码实践
|
||||
- 解释为什么需要修改
|
||||
|
||||
## 🔧 自动化脚本
|
||||
|
||||
完整的审查脚本:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# comprehensive-review.sh - 全面代码审查脚本
|
||||
|
||||
set -e
|
||||
|
||||
PR_ID=${1:-123}
|
||||
OWNER=${2:-"myuser"}
|
||||
REPO=${3:-"myrepo"}
|
||||
|
||||
echo "=== 开始全面审查 PR #$PR_ID ==="
|
||||
|
||||
# 步骤 1:获取数据
|
||||
echo "步骤 1:获取 PR 数据..."
|
||||
gitlink-cli pr +view --id $PR_ID --format json > pr_info.json
|
||||
gitlink-cli pr +files --id $PR_ID --format json > pr_files.json
|
||||
gitlink-cli pr +diff --id $PR_ID --format json > pr_diff.json
|
||||
|
||||
# 步骤 2:验证数据
|
||||
echo "步骤 2:验证数据..."
|
||||
if [ "$(cat pr_info.json | jq '.ok')" != "true" ]; then
|
||||
echo "错误:无法获取 PR 信息"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 步骤 3:组织分析数据
|
||||
echo "步骤 3:组织分析数据..."
|
||||
cat > analysis_input.json <<EOF
|
||||
{
|
||||
"pr_info": $(cat pr_info.json | jq '.data'),
|
||||
"files": $(cat pr_files.json | jq '.data.files'),
|
||||
"diff": $(cat pr_diff.json | jq -r '.data.diff')
|
||||
}
|
||||
EOF
|
||||
|
||||
# 步骤 4:AI 分析
|
||||
echo "步骤 4:AI 分析(需要 Claude Code 或 API)..."
|
||||
# 这里调用 AI 分析工具
|
||||
# claude-code-analyze analysis_input.json > analysis_result.json
|
||||
|
||||
# 步骤 5:生成报告
|
||||
echo "步骤 5:生成审查报告..."
|
||||
# cat analysis_result.json | jq -r '.content' > review_report.json
|
||||
# cat analysis_result.json | jq -r '.content' > review_report.md
|
||||
|
||||
# 步骤 6:输出报告
|
||||
echo "步骤 6:输出审查报告..."
|
||||
# cat review_report.md
|
||||
|
||||
echo "=== 审查完成 ==="
|
||||
```
|
||||
|
||||
使用方法:
|
||||
```bash
|
||||
chmod +x comprehensive-review.sh
|
||||
./comprehensive-review.sh 123 myuser myrepo
|
||||
```
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [基础审查工作流](basic-review-workflow.md) - 快速代码审查
|
||||
- [自动审查工作流](auto-review-pr.md) - AI 自动审查
|
||||
- [代码质量检查](../references/code-review-quality.md) - 质量分析详解
|
||||
- [安全性检查](../references/code-review-security.md) - 安全分析详解
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
# 代码变更分析
|
||||
|
||||
本文档详细说明如何使用 gitlink-cli 分析 PR 的代码变更。
|
||||
|
||||
## 📋 概述
|
||||
|
||||
代码变更分析是智能代码审查的第一步,通过获取 PR 的文件列表和 diff 内容,为后续的 AI 分析提供数据基础。
|
||||
|
||||
## 🎯 分析流程
|
||||
|
||||
```
|
||||
开始
|
||||
↓
|
||||
1. 获取 PR 基本信息
|
||||
├─ 使用 pr +view 获取 PR 详情
|
||||
└─ 确认 PR 存在且可访问
|
||||
↓
|
||||
2. 获取变更文件列表
|
||||
├─ 使用 pr +files 获取文件列表
|
||||
└─ 识别新增/修改/删除的文件
|
||||
↓
|
||||
3. 获取 diff 内容
|
||||
├─ 使用 pr +diff 获取完整 diff
|
||||
└─ 解析代码变更详情
|
||||
↓
|
||||
4. 数据预处理
|
||||
├─ 过滤无关文件(如二进制文件)
|
||||
├─ 提取代码片段
|
||||
└─ 组织分析数据
|
||||
↓
|
||||
完成
|
||||
```
|
||||
|
||||
## 🔧 步骤详解
|
||||
|
||||
### 步骤 1:获取 PR 基本信息
|
||||
|
||||
**目的**: 确认 PR 存在且可访问,获取 PR 的元数据信息。
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +view --id <pr_id> --format json
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
# 获取 PR #123 的基本信息
|
||||
gitlink-cli pr +view --id 123 --format json
|
||||
```
|
||||
|
||||
**返回结果**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"id": 123,
|
||||
"project_issues_index": 123,
|
||||
"title": "Feature: Add user authentication",
|
||||
"body": "This PR adds user authentication...",
|
||||
"author": {
|
||||
"login": "developer",
|
||||
"user_id": 456
|
||||
},
|
||||
"status": "open",
|
||||
"pull_request_status": 0,
|
||||
"head": "feature/auth",
|
||||
"base": "main",
|
||||
"created_at": "2026-06-12T10:00:00Z",
|
||||
"updated_at": "2026-06-12T10:30:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键信息提取**:
|
||||
- `id`: PR 数据库 ID(用于后续 API 调用)
|
||||
- `project_issues_index`: PR 编号(网页显示)
|
||||
- `title`: PR 标题
|
||||
- `author`: 作者信息
|
||||
- `status`: PR 状态(open/closed/merged)
|
||||
- `head` / `base`: 分支信息
|
||||
|
||||
### 步骤 2:获取变更文件列表
|
||||
|
||||
**目的**: 获取 PR 中所有变更的文件列表,了解代码变更的范围。
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +files --id <pr_id> --format json
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
# 获取 PR #123 的变更文件列表
|
||||
gitlink-cli pr +files --id 123 --format json
|
||||
```
|
||||
|
||||
**返回结果**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"files": [
|
||||
{
|
||||
"filename": "src/auth/login.go",
|
||||
"status": "modified",
|
||||
"additions": 50,
|
||||
"deletions": 20,
|
||||
"changes": 70,
|
||||
"patch": "@@ -1,10 +1,15 @@\n+func login() {"
|
||||
},
|
||||
{
|
||||
"filename": "src/auth/user.go",
|
||||
"status": "added",
|
||||
"additions": 80,
|
||||
"deletions": 0,
|
||||
"changes": 80,
|
||||
"patch": "+package auth\n+\n+func User() {"
|
||||
},
|
||||
{
|
||||
"filename": "README.md",
|
||||
"status": "modified",
|
||||
"additions": 5,
|
||||
"deletions": 2,
|
||||
"changes": 7,
|
||||
"patch": "@@ -1,5 +1,7 @@\n+## Usage\n ..."
|
||||
}
|
||||
],
|
||||
"total_files": 3,
|
||||
"total_additions": 135,
|
||||
"total_deletions": 22,
|
||||
"total_changes": 157
|
||||
}
|
||||
```
|
||||
|
||||
**文件状态说明**:
|
||||
- `added`: 新增文件
|
||||
- `modified`: 修改文件
|
||||
- `deleted`: 删除文件
|
||||
- `renamed`: 重命名文件
|
||||
|
||||
**统计信息**:
|
||||
- `total_files`: 变更文件总数
|
||||
- `total_additions`: 新增行数
|
||||
- `total_deletions`: 删除行数
|
||||
- `total_changes`: 总变更行数
|
||||
|
||||
### 步骤 3:获取 diff 内容
|
||||
|
||||
**目的**: 获取 PR 的完整 diff 内容,用于 AI 代码分析。
|
||||
|
||||
**命令**:
|
||||
```bash
|
||||
gitlink-cli pr +diff --id <pr_id> --format json
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```bash
|
||||
# 获取 PR #123 的 diff 内容
|
||||
gitlink-cli pr +diff --id 123 --format json
|
||||
```
|
||||
|
||||
**返回结果**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"data": {
|
||||
"diff": "diff --git a/src/auth/login.go b/src/auth/login.go\nindex 1234567..abcdefg 100644\n--- a/src/auth/login.go\n+++ b/src/auth/login.go\n@@ -1,10 +1,15 @@\n package auth\n\n+func login(username, password string) error {\n+\tdb, _ := sql.Open(\"mysql\", dsn)\n+\tquery := \"SELECT * FROM users WHERE username = '\" + username + \"'\"\n+\t...\n+}\n",
|
||||
"files_count": 3,
|
||||
"additions": 135,
|
||||
"deletions": 22
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**diff 格式说明**:
|
||||
- 标准 unified diff 格式
|
||||
- 包含文件头、变更块、代码行
|
||||
- `+` 表示新增行
|
||||
- `-` 表示删除行
|
||||
|
||||
### 步骤 4:数据预处理
|
||||
|
||||
**目的**: 清理和组织数据,为 AI 分析做准备。
|
||||
|
||||
#### 4.1 过滤无关文件
|
||||
|
||||
**需要过滤的文件类型**:
|
||||
- 二进制文件(图片、字体、压缩包)
|
||||
- 配置文件(package.json、tsconfig.json)
|
||||
- 文档文件(README.md、CHANGELOG.md)
|
||||
- 测试文件(*_test.go、*.spec.js)
|
||||
|
||||
**过滤规则**:
|
||||
```javascript
|
||||
const shouldSkip = (filename) => {
|
||||
// 跳过二进制文件
|
||||
const binaryExts = ['.png', '.jpg', '.gif', '.pdf', '.zip', '.exe'];
|
||||
if (binaryExts.some(ext => filename.endsWith(ext))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 跳过配置文件
|
||||
const configFiles = ['package.json', 'tsconfig.json', '.gitignore'];
|
||||
if (configFiles.includes(filename)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 跳过文档文件
|
||||
if (filename.match(/^(README|CHANGELOG|CONTRIBUTING)\.md$/i)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.2 提取代码片段
|
||||
|
||||
**目的**: 从 diff 中提取变更的代码片段,便于 AI 分析。
|
||||
|
||||
**示例**:
|
||||
```javascript
|
||||
const extractCodeSnippets = (diff) => {
|
||||
const lines = diff.split('\n');
|
||||
const snippets = [];
|
||||
let currentSnippet = [];
|
||||
let inHunk = false;
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.startsWith('@@')) {
|
||||
// 开始新的代码块
|
||||
if (currentSnippet.length > 0) {
|
||||
snippets.push(currentSnippet.join('\n'));
|
||||
}
|
||||
currentSnippet = [line];
|
||||
inHunk = true;
|
||||
} else if (inHunk && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
|
||||
// 收集代码行
|
||||
currentSnippet.push(line);
|
||||
}
|
||||
});
|
||||
|
||||
if (currentSnippet.length > 0) {
|
||||
snippets.push(currentSnippet.join('\n'));
|
||||
}
|
||||
|
||||
return snippets;
|
||||
};
|
||||
```
|
||||
|
||||
#### 4.3 组织分析数据
|
||||
|
||||
**最终数据结构**:
|
||||
```json
|
||||
{
|
||||
"pr_info": {
|
||||
"id": 123,
|
||||
"title": "Feature: Add user authentication",
|
||||
"author": "developer",
|
||||
"branch": "feature/auth → main"
|
||||
},
|
||||
"files": [
|
||||
{
|
||||
"filename": "src/auth/login.go",
|
||||
"status": "modified",
|
||||
"language": "go",
|
||||
"code_snippets": [
|
||||
{
|
||||
"start_line": 10,
|
||||
"end_line": 25,
|
||||
"code": "+func login(username, password string) error {"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"statistics": {
|
||||
"total_files": 3,
|
||||
"code_files": 2,
|
||||
"total_additions": 135,
|
||||
"total_deletions": 22
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 按文件类型分组
|
||||
|
||||
将变更文件按语言和类型分组,便于针对性分析:
|
||||
|
||||
```javascript
|
||||
const groupFilesByLanguage = (files) => {
|
||||
const groups = {
|
||||
go: [],
|
||||
javascript: [],
|
||||
python: [],
|
||||
other: []
|
||||
};
|
||||
|
||||
files.forEach(file => {
|
||||
const ext = file.filename.split('.').pop();
|
||||
const lang = detectLanguage(ext);
|
||||
groups[lang].push(file);
|
||||
});
|
||||
|
||||
return groups;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 优先审查核心文件
|
||||
|
||||
优先审查核心业务逻辑文件:
|
||||
|
||||
```javascript
|
||||
const prioritizeFiles = (files) => {
|
||||
const priority = {
|
||||
'high': [], // 核心业务逻辑
|
||||
'medium': [], // 工具函数
|
||||
'low': [] // 配置、测试
|
||||
};
|
||||
|
||||
files.forEach(file => {
|
||||
if (file.filename.includes('core') || file.filename.includes('service')) {
|
||||
priority.high.push(file);
|
||||
} else if (file.filename.includes('util') || file.filename.includes('helper')) {
|
||||
priority.medium.push(file);
|
||||
} else {
|
||||
priority.low.push(file);
|
||||
}
|
||||
});
|
||||
|
||||
return priority;
|
||||
};
|
||||
```
|
||||
|
||||
### 3. 限制分析范围
|
||||
|
||||
对于大型 PR,限制分析范围:
|
||||
|
||||
```javascript
|
||||
const limitAnalysisScope = (files, maxFiles = 10, maxLines = 1000) => {
|
||||
let totalLines = 0;
|
||||
const selectedFiles = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (selectedFiles.length >= maxFiles) break;
|
||||
if (totalLines + file.changes > maxLines) break;
|
||||
|
||||
selectedFiles.push(file);
|
||||
totalLines += file.changes;
|
||||
}
|
||||
|
||||
return selectedFiles;
|
||||
};
|
||||
```
|
||||
|
||||
## 🔍 常见问题
|
||||
|
||||
### Q: 如何处理大型 PR?
|
||||
|
||||
**A**: 大型 PR(>1000 行)建议:
|
||||
1. 按模块分组分析
|
||||
2. 优先审查核心文件
|
||||
3. 分批生成审查报告
|
||||
4. 建议作者拆分为多个小 PR
|
||||
|
||||
### Q: 如何处理重命名文件?
|
||||
|
||||
**A**: GitLink 的 PR API 会正确处理重命名:
|
||||
- `status` 为 `renamed`
|
||||
- `patch` 包含重命名前后的完整路径
|
||||
- 分析时使用新文件名
|
||||
|
||||
### Q: 如何检测文件语言?
|
||||
|
||||
**A**: 使用文件扩展名检测:
|
||||
|
||||
```javascript
|
||||
const detectLanguage = (filename) => {
|
||||
const ext = filename.split('.').pop();
|
||||
const languageMap = {
|
||||
'go': 'go',
|
||||
'js': 'javascript',
|
||||
'ts': 'typescript',
|
||||
'py': 'python',
|
||||
'java': 'java',
|
||||
'rb': 'ruby',
|
||||
'php': 'php'
|
||||
};
|
||||
return languageMap[ext] || 'other';
|
||||
};
|
||||
```
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [代码质量检查](code-review-quality.md) - 代码质量分析
|
||||
- [安全性检查](code-review-security.md) - 安全性分析
|
||||
- [性能检查](code-review-performance.md) - 性能分析
|
||||
- [完整工作流](../examples/comprehensive-review-workflow.md) - 完整审查流程
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,388 @@
|
|||
# 代码质量检查
|
||||
|
||||
本文档详细说明如何使用 AI 分析代码质量问题。
|
||||
|
||||
## 📋 概述
|
||||
|
||||
代码质量检查是智能代码审查的核心维度之一,通过分析代码的复杂度、命名规范、注释完整性等指标,评估代码的可读性和可维护性。
|
||||
|
||||
## 🎯 检查维度
|
||||
|
||||
### 1. 代码复杂度
|
||||
|
||||
**检查项**:
|
||||
- **圈复杂度(Cyclomatic Complexity)**: 衡量代码的独立路径数量
|
||||
- **函数长度**: 单个函数的代码行数
|
||||
- **嵌套层级**: 代码的嵌套深度
|
||||
- **参数数量**: 函数的参数个数
|
||||
|
||||
**标准**:
|
||||
- 圈复杂度 < 10: 优秀 ✅
|
||||
- 圈复杂度 10-20: 良好 ⚠️
|
||||
- 圈复杂度 > 20: 需要重构 🔴
|
||||
|
||||
- 函数长度 < 50 行: 优秀 ✅
|
||||
- 函数长度 50-100 行: 良好 ⚠️
|
||||
- 函数长度 > 100 行: 需要拆分 🔴
|
||||
|
||||
- 嵌套层级 < 3: 优秀 ✅
|
||||
- 嵌套层级 3-4: 良好 ⚠️
|
||||
- 嵌套层级 > 4: 需要简化 🔴
|
||||
|
||||
**示例代码**:
|
||||
```go
|
||||
// 🔴 高复杂度示例(需要重构)
|
||||
func processData(input1, input2, input3, input4, input5 string) error {
|
||||
if input1 != "" {
|
||||
for i := 0; i < 100; i++ {
|
||||
if input2 != "" {
|
||||
switch input3 {
|
||||
case "a":
|
||||
if input4 != "" {
|
||||
// 嵌套层级过深
|
||||
}
|
||||
case "b":
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ✅ 低复杂度示例(优秀)
|
||||
func processData(input string) error {
|
||||
if err := validateInput(input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := parseInput(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return saveData(data)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 命名规范
|
||||
|
||||
**检查项**:
|
||||
- **变量命名**: 是否使用清晰、描述性的名称
|
||||
- **函数命名**: 是否使用动词开头,描述函数功能
|
||||
- **类命名**: 是否使用名词,首字母大写
|
||||
- **常量命名**: 是否使用全大写+下划线
|
||||
|
||||
**规则**:
|
||||
- ✅ 使用有意义的名称(`userAge` 而非 `x`)
|
||||
- ✅ 遵循语言约定(Go: 驼峰命名,Python: 下划线命名)
|
||||
- ❌ 避免单字母变量(除循环变量 `i`, `j`)
|
||||
- ❌ 避免缩写(`usr` 而非 `user`)
|
||||
|
||||
**示例**:
|
||||
```javascript
|
||||
// ❌ 不好的命名
|
||||
const x = 10;
|
||||
function calc(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// ✅ 好的命名
|
||||
const maxRetryCount = 10;
|
||||
function calculateTotal(price, quantity) {
|
||||
return price * quantity;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 注释完整性
|
||||
|
||||
**检查项**:
|
||||
- **函数注释**: 复杂函数是否有注释说明
|
||||
- **代码逻辑**: 复杂逻辑是否有解释
|
||||
- **TODO 标记**: 是否有未完成的 TODO
|
||||
|
||||
**规则**:
|
||||
- ✅ 公共 API 必须有注释
|
||||
- ✅ 复杂算法必须有注释
|
||||
- ✅ 非显而易见的逻辑必须有注释
|
||||
- ❌ 避免注释显而易见的代码
|
||||
|
||||
**示例**:
|
||||
```go
|
||||
// ❌ 不好的注释(显而易见)
|
||||
// 设置用户名为 "admin"
|
||||
username := "admin"
|
||||
|
||||
// ✅ 好的注释(解释复杂逻辑)
|
||||
// 使用二次探测法解决哈希冲突
|
||||
index := (hash + i * i) % tableSize
|
||||
```
|
||||
|
||||
### 4. 代码格式
|
||||
|
||||
**检查项**:
|
||||
- **缩进**: 是否使用一致的缩进(2/4 空格或 Tab)
|
||||
- **空行**: 函数/类之间是否有适当的空行
|
||||
- **行长度**: 单行代码是否过长(建议 < 120 字符)
|
||||
- **代码组织**: 导入、常量、变量、函数的顺序
|
||||
|
||||
**标准**:
|
||||
- 使用统一的代码格式化工具(gofmt、prettier)
|
||||
- 函数之间空 1-2 行
|
||||
- 逻辑块之间空 1 行
|
||||
|
||||
## 🔧 分析流程
|
||||
|
||||
```
|
||||
开始分析代码质量
|
||||
↓
|
||||
1. 解析代码结构
|
||||
├─ 识别函数、类、变量
|
||||
└─ 提取代码块
|
||||
↓
|
||||
2. 计算复杂度指标
|
||||
├─ 圈复杂度
|
||||
├─ 函数长度
|
||||
├─ 嵌套层级
|
||||
└─ 参数数量
|
||||
↓
|
||||
3. 检查命名规范
|
||||
├─ 变量命名
|
||||
├─ 函数命名
|
||||
└─ 类命名
|
||||
↓
|
||||
4. 评估注释完整性
|
||||
├─ 函数注释
|
||||
├─ 逻辑注释
|
||||
└─ TODO 标记
|
||||
↓
|
||||
5. 生成质量报告
|
||||
├─ 评分
|
||||
├─ 问题列表
|
||||
└─ 改进建议
|
||||
↓
|
||||
完成
|
||||
```
|
||||
|
||||
## 📊 输出格式
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"quality_analysis": {
|
||||
"overall_score": 85,
|
||||
"complexity": {
|
||||
"score": 90,
|
||||
"metrics": {
|
||||
"avg_cyclomatic_complexity": 3.5,
|
||||
"max_cyclomatic_complexity": 8,
|
||||
"avg_function_length": 25,
|
||||
"max_function_length": 60,
|
||||
"max_nesting_level": 3
|
||||
},
|
||||
"issues": [
|
||||
{
|
||||
"file": "src/auth/login.go",
|
||||
"function": "authenticate",
|
||||
"line": 45,
|
||||
"severity": "MEDIUM",
|
||||
"metric": "function_length",
|
||||
"value": 60,
|
||||
"threshold": 50,
|
||||
"suggestion": "建议将此函数拆分为更小的函数"
|
||||
}
|
||||
]
|
||||
},
|
||||
"naming": {
|
||||
"score": 95,
|
||||
"issues": [
|
||||
{
|
||||
"file": "src/auth/user.go",
|
||||
"line": 78,
|
||||
"severity": "LOW",
|
||||
"type": "variable",
|
||||
"name": "x",
|
||||
"suggestion": "建议使用更具描述性的名称,如 'retryCount'"
|
||||
}
|
||||
]
|
||||
},
|
||||
"comments": {
|
||||
"score": 75,
|
||||
"coverage": 60,
|
||||
"missing_comments": [
|
||||
{
|
||||
"file": "src/auth/login.go",
|
||||
"function": "validateToken",
|
||||
"line": 120,
|
||||
"suggestion": "建议添加函数注释说明验证逻辑"
|
||||
}
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"score": 90,
|
||||
"issues": [
|
||||
{
|
||||
"file": "src/auth/login.go",
|
||||
"line": 45,
|
||||
"type": "line_length",
|
||||
"value": 150,
|
||||
"threshold": 120,
|
||||
"suggestion": "建议将长行拆分为多行"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown 格式
|
||||
|
||||
```markdown
|
||||
## 代码质量分析: 85/100 ⭐⭐⭐⭐
|
||||
|
||||
### 复杂度: 90/100 ✅
|
||||
|
||||
- 平均圈复杂度: 3.5 ✅
|
||||
- 最大圈复杂度: 8 ✅
|
||||
- 平均函数长度: 25 行 ✅
|
||||
- 最大函数长度: 60 行 ⚠️
|
||||
- 最大嵌套层级: 3 ✅
|
||||
|
||||
#### ⚠️ 需要改进
|
||||
|
||||
1. **函数过长** - `src/auth/login.go:45`
|
||||
- 函数 `authenticate` 长度为 60 行
|
||||
- 建议:将此函数拆分为更小的函数
|
||||
|
||||
### 命名规范: 95/100 ✅
|
||||
|
||||
#### 💡 改进建议
|
||||
|
||||
1. **变量命名** - `src/auth/user.go:78`
|
||||
- 变量 `x` 命名不够清晰
|
||||
- 建议:使用更具描述性的名称,如 'retryCount'
|
||||
|
||||
### 注释完整性: 75/100 ⚠️
|
||||
|
||||
- 注释覆盖率: 60%
|
||||
|
||||
#### ❌ 缺少注释
|
||||
|
||||
1. **函数注释** - `src/auth/login.go:120`
|
||||
- 函数 `validateToken` 缺少注释
|
||||
- 建议:添加函数注释说明验证逻辑
|
||||
|
||||
### 代码格式: 90/100 ✅
|
||||
|
||||
#### 💡 改进建议
|
||||
|
||||
1. **行长度** - `src/auth/login.go:45`
|
||||
- 行长度为 150 字符
|
||||
- 建议:将长行拆分为多行
|
||||
```
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 保持函数简短
|
||||
|
||||
```go
|
||||
// ✅ 好的实践
|
||||
func handleRequest(req *Request) (*Response, error) {
|
||||
if err := validateRequest(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := processRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildResponse(data), nil
|
||||
}
|
||||
|
||||
// ❌ 不好的实践
|
||||
func handleRequest(req *Request) (*Response, error) {
|
||||
// 100+ 行代码
|
||||
// 验证、处理、响应都在一个函数中
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用清晰的命名
|
||||
|
||||
```javascript
|
||||
// ✅ 好的实践
|
||||
const MAX_RETRY_ATTEMPTS = 3;
|
||||
const API_TIMEOUT_MS = 5000;
|
||||
|
||||
function calculateDiscount(price, discountRate) {
|
||||
return price * (1 - discountRate);
|
||||
}
|
||||
|
||||
// ❌ 不好的实践
|
||||
const max = 3;
|
||||
const t = 5000;
|
||||
|
||||
function calc(p, d) {
|
||||
return p * (1 - d);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 添加有意义的注释
|
||||
|
||||
```python
|
||||
# ✅ 好的注释
|
||||
# 实现二分查找算法,时间复杂度 O(log n)
|
||||
def binary_search(arr, target):
|
||||
left, right = 0, len(arr) - 1
|
||||
while left <= right:
|
||||
mid = (left + right) // 2
|
||||
if arr[mid] == target:
|
||||
return mid
|
||||
elif arr[mid] < target:
|
||||
left = mid + 1
|
||||
else:
|
||||
right = mid - 1
|
||||
return -1
|
||||
|
||||
# ❌ 不好的注释
|
||||
# 查找目标值
|
||||
def binary_search(arr, target):
|
||||
# ... 显而易见的代码 ...
|
||||
```
|
||||
|
||||
## 🔍 常见问题
|
||||
|
||||
### Q: 如何平衡代码质量和开发效率?
|
||||
|
||||
**A**:
|
||||
- 对于核心业务逻辑,严格要求代码质量
|
||||
- 对于一次性脚本,可以适当放宽标准
|
||||
- 使用代码格式化工具自动处理格式问题
|
||||
- 定期进行代码重构,而非过度追求完美
|
||||
|
||||
### Q: 如何处理历史遗留的低质量代码?
|
||||
|
||||
**A**:
|
||||
- 不要求立即重构所有历史代码
|
||||
- 在修改相关代码时进行重构
|
||||
- 优先重构最常用的核心模块
|
||||
- 逐步改进,避免大规模重写
|
||||
|
||||
### Q: 代码质量工具与 AI 审查如何配合?
|
||||
|
||||
**A**:
|
||||
- 代码质量工具(lint、static analysis)处理规则性检查
|
||||
- AI 审查处理语义性、上下文相关的检查
|
||||
- 工具提供定量指标,AI 提供定性分析
|
||||
- 结合使用,获得全面的代码质量评估
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [安全性检查](code-review-security.md) - 安全性分析
|
||||
- [性能检查](code-review-performance.md) - 性能分析
|
||||
- [可维护性检查](code-review-maintainability.md) - 可维护性分析
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,520 @@
|
|||
# 安全性检查
|
||||
|
||||
本文档详细说明如何使用 AI 分析代码安全问题。
|
||||
|
||||
## 📋 概述
|
||||
|
||||
安全性检查是智能代码审查的关键维度,通过识别常见的安全漏洞和风险,帮助开发者提升代码安全性,防止潜在的安全攻击。
|
||||
|
||||
## 🎯 检查维度
|
||||
|
||||
### 1. SQL 注入(SQL Injection)
|
||||
|
||||
**风险等级**: 🔴 HIGH
|
||||
|
||||
**描述**: 攻击者通过恶意构造的输入篡改数据库查询逻辑。
|
||||
|
||||
**检测模式**:
|
||||
- 字符串拼接 SQL 语句
|
||||
- 直接使用用户输入构造查询
|
||||
- 未使用参数化查询
|
||||
|
||||
**示例**:
|
||||
```go
|
||||
// ❌ 存在 SQL 注入风险
|
||||
query := "SELECT * FROM users WHERE username = '" + username + "'"
|
||||
db.Query(query)
|
||||
|
||||
// ✅ 安全的参数化查询
|
||||
query := "SELECT * FROM users WHERE username = ?"
|
||||
db.Query(query, username)
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 使用参数化查询或 ORM
|
||||
2. 对用户输入进行验证和转义
|
||||
3. 使用最小权限的数据库账户
|
||||
|
||||
### 2. XSS 跨站脚本(Cross-Site Scripting)
|
||||
|
||||
**风险等级**: 🔴 HIGH
|
||||
|
||||
**描述**: 攻击者在网页中注入恶意脚本,窃取用户信息或进行攻击。
|
||||
|
||||
**检测模式**:
|
||||
- 直接输出用户输入到 HTML
|
||||
- 未对用户输入进行 HTML 转义
|
||||
- 使用 `innerHTML` 直接插入用户内容
|
||||
|
||||
**示例**:
|
||||
```javascript
|
||||
// ❌ 存在 XSS 风险
|
||||
div.innerHTML = userComment;
|
||||
document.write(userName);
|
||||
|
||||
// ✅ 安全的 HTML 转义
|
||||
div.textContent = userComment;
|
||||
div.innerHTML = escapeHtml(userComment);
|
||||
|
||||
function escapeHtml(text) {
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 对用户输入进行 HTML 转义
|
||||
2. 使用 `textContent` 而非 `innerHTML`
|
||||
3. 使用 CSP(Content Security Policy)
|
||||
4. 对输出进行白名单验证
|
||||
|
||||
### 3. 敏感信息泄露(Sensitive Data Exposure)
|
||||
|
||||
**风险等级**: 🔴 HIGH
|
||||
|
||||
**描述**: 代码中包含硬编码的密钥、密码、Token 等敏感信息。
|
||||
|
||||
**检测模式**:
|
||||
- 硬编码的密码、密钥、Token
|
||||
- 代码中包含 API 密钥
|
||||
- 敏感配置信息
|
||||
|
||||
**示例**:
|
||||
```go
|
||||
// ❌ 硬编码敏感信息
|
||||
const (
|
||||
DB_PASSWORD = "admin123"
|
||||
API_KEY = "sk-1234567890abcdef"
|
||||
SECRET_KEY = "my-secret-key"
|
||||
)
|
||||
|
||||
// ✅ 使用环境变量
|
||||
dbPassword := os.Getenv("DB_PASSWORD")
|
||||
apiKey := os.Getenv("API_KEY")
|
||||
secretKey := os.Getenv("SECRET_KEY")
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 使用环境变量存储敏感信息
|
||||
2. 使用配置管理工具(如 Vault)
|
||||
3. 不要在代码中硬编码密钥
|
||||
4. 使用 `.env` 文件并加入 `.gitignore`
|
||||
|
||||
### 4. 认证和授权问题(Authentication & Authorization)
|
||||
|
||||
**风险等级**: 🔴 HIGH
|
||||
|
||||
**描述**: 认证或授权机制存在缺陷,导致未授权访问。
|
||||
|
||||
**检测模式**:
|
||||
- 缺少认证检查
|
||||
- 权限验证不充分
|
||||
- 会话管理不当
|
||||
|
||||
**示例**:
|
||||
```go
|
||||
// ❌ 缺少权限检查
|
||||
func getUserProfile(userID int) (*User, error) {
|
||||
return db.GetUser(userID)
|
||||
}
|
||||
|
||||
// ✅ 添加权限检查
|
||||
func getUserProfile(userID int, currentUser *User) (*User, error) {
|
||||
// 检查是否有权限访问该用户信息
|
||||
if currentUser.ID != userID && !currentUser.IsAdmin {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
return db.GetUser(userID)
|
||||
}
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 每个敏感操作都要进行权限检查
|
||||
2. 使用最小权限原则
|
||||
3. 实施适当的会话管理
|
||||
4. 定期轮换密钥和证书
|
||||
|
||||
### 5. 输入验证(Input Validation)
|
||||
|
||||
**风险等级**: ⚠️ MEDIUM
|
||||
|
||||
**描述**: 对用户输入缺少充分的验证,可能导致各种安全问题。
|
||||
|
||||
**检测模式**:
|
||||
- 缺少输入长度检查
|
||||
- 缺少输入格式验证
|
||||
- 缺少类型检查
|
||||
|
||||
**示例**:
|
||||
```javascript
|
||||
// ❌ 缺少输入验证
|
||||
function createUser(username, password) {
|
||||
db.insert({ username, password });
|
||||
}
|
||||
|
||||
// ✅ 添加输入验证
|
||||
function createUser(username, password) {
|
||||
if (!username || username.length < 3 || username.length > 20) {
|
||||
throw new Error('用户名长度必须在 3-20 个字符之间');
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
||||
throw new Error('用户名只能包含字母、数字和下划线');
|
||||
}
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
throw new Error('密码长度至少为 8 个字符');
|
||||
}
|
||||
|
||||
db.insert({ username, password });
|
||||
}
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 验证输入长度、格式、类型
|
||||
2. 使用白名单而非黑名单
|
||||
3. 在客户端和服务端都进行验证
|
||||
4. 对不同来源的输入都要验证
|
||||
|
||||
### 6. 资源泄漏(Resource Leak)
|
||||
|
||||
**风险等级**: ⚠️ MEDIUM
|
||||
|
||||
**描述**: 资源(文件、连接、内存)未正确释放,可能导致 DoS。
|
||||
|
||||
**检测模式**:
|
||||
- 文件打开后未关闭
|
||||
- 数据库连接未关闭
|
||||
- 网络连接未关闭
|
||||
|
||||
**示例**:
|
||||
```go
|
||||
// ❌ 资源未关闭
|
||||
func processData(filename string) error {
|
||||
file, _ := os.Open(filename)
|
||||
// 处理文件
|
||||
// 忘记关闭文件
|
||||
|
||||
db, _ := sql.Open("mysql", dsn)
|
||||
// 处理数据库
|
||||
// 忘记关闭连接
|
||||
}
|
||||
|
||||
// ✅ 使用 defer 确保资源关闭
|
||||
func processData(filename string) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
db, err := sql.Open("mysql", dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// 处理文件和数据库
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 使用 `defer` 确保资源释放
|
||||
2. 使用 `try-with-resources`(Java)
|
||||
3. 使用连接池管理数据库连接
|
||||
4. 定期检查和清理资源
|
||||
|
||||
### 7. 不安全的随机数(Insecure Randomness)
|
||||
|
||||
**风险等级**: ⚠️ MEDIUM
|
||||
|
||||
**描述**: 使用可预测的随机数生成器,可能被攻击者预测。
|
||||
|
||||
**检测模式**:
|
||||
- 使用 `Math.random()` 生成安全相关随机数
|
||||
- 使用时间戳作为随机种子
|
||||
- 使用线性同余生成器
|
||||
|
||||
**示例**:
|
||||
```javascript
|
||||
// ❌ 不安全的随机数
|
||||
const token = Math.random().toString(36);
|
||||
const seed = Date.now();
|
||||
const random = srand(seed);
|
||||
|
||||
// ✅ 安全的随机数
|
||||
const crypto = require('crypto');
|
||||
const token = crypto.randomBytes(16).toString('hex');
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 使用加密安全的随机数生成器
|
||||
2. 不要使用时间戳作为随机种子
|
||||
3. 对于密钥、Token 等安全相关数据,使用 CSPRNG
|
||||
|
||||
### 8. 不安全的反序列化(Insecure Deserialization)
|
||||
|
||||
**风险等级**: 🔴 HIGH
|
||||
|
||||
**描述**: 反序列化不受信任的数据可能导致远程代码执行。
|
||||
|
||||
**检测模式**:
|
||||
- 反序列化用户输入
|
||||
- 使用不安全的序列化格式
|
||||
- 缺少完整性验证
|
||||
|
||||
**示例**:
|
||||
```java
|
||||
// ❌ 不安全的反序列化
|
||||
Object obj = deserializeObject(userInput);
|
||||
|
||||
// ✅ 安全的反序列化
|
||||
// 1. 使用白名单限制可反序列化的类型
|
||||
// 2. 验证数据的完整性
|
||||
// 3. 使用安全的序列化格式(如 JSON)
|
||||
```
|
||||
|
||||
**修复建议**:
|
||||
1. 避免反序列化不受信任的数据
|
||||
2. 使用白名单限制可反序列化的类型
|
||||
3. 使用安全的序列化格式(如 JSON)
|
||||
4. 验证数据的完整性和来源
|
||||
|
||||
## 🔧 分析流程
|
||||
|
||||
```
|
||||
开始安全性分析
|
||||
↓
|
||||
1. 解析代码结构
|
||||
├─ 识别数据库操作
|
||||
├─ 识别用户输入处理
|
||||
└─ 识别敏感信息
|
||||
↓
|
||||
2. 检测安全漏洞
|
||||
├─ SQL 注入
|
||||
├─ XSS 跨站脚本
|
||||
├─ 敏感信息泄露
|
||||
├─ 认证授权问题
|
||||
├─ 输入验证
|
||||
├─ 资源泄漏
|
||||
├─ 不安全的随机数
|
||||
└─ 不安全的反序列化
|
||||
↓
|
||||
3. 评估风险等级
|
||||
├─ 根据漏洞类型评估
|
||||
├─ 根据上下文评估
|
||||
└─ 根据影响范围评估
|
||||
↓
|
||||
4. 生成安全报告
|
||||
├─ 漏洞列表
|
||||
├─ 风险等级
|
||||
└─ 修复建议
|
||||
↓
|
||||
完成
|
||||
```
|
||||
|
||||
## 📊 输出格式
|
||||
|
||||
### JSON 格式
|
||||
|
||||
```json
|
||||
{
|
||||
"security_analysis": {
|
||||
"overall_score": 70,
|
||||
"status": "NEEDS_REVIEW",
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"id": 1,
|
||||
"severity": "HIGH",
|
||||
"category": "sql_injection",
|
||||
"title": "SQL 注入漏洞",
|
||||
"file": "src/auth/login.go",
|
||||
"line": 45,
|
||||
"code_snippet": "query := \"SELECT * FROM users WHERE username = '\" + username + \"'\"",
|
||||
"description": "直接拼接用户输入到 SQL 语句,存在 SQL 注入风险",
|
||||
"impact": "攻击者可以通过构造恶意输入访问或篡改数据库",
|
||||
"recommendation": "使用参数化查询或 ORM",
|
||||
"references": [
|
||||
"https://owasp.org/www-community/attacks/SQL_Injection",
|
||||
"https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"severity": "HIGH",
|
||||
"category": "sensitive_data",
|
||||
"title": "敏感信息泄露",
|
||||
"file": "config/database.go",
|
||||
"line": 10,
|
||||
"code_snippet": "const DB_PASSWORD = \"admin123\"",
|
||||
"description": "代码中硬编码数据库密码",
|
||||
"impact": "敏感信息可能被泄露,导致数据库被攻击",
|
||||
"recommendation": "使用环境变量或配置管理工具存储敏感信息",
|
||||
"references": [
|
||||
"https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html"
|
||||
]
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"total": 5,
|
||||
"critical": 0,
|
||||
"high": 2,
|
||||
"medium": 3,
|
||||
"low": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown 格式
|
||||
|
||||
```markdown
|
||||
## 安全性分析: 70/100 ⚠️
|
||||
|
||||
### 🔴 高危漏洞(2)
|
||||
|
||||
#### 1. SQL 注入漏洞
|
||||
- **文件**: `src/auth/login.go:45`
|
||||
- **风险等级**: 🔴 HIGH
|
||||
- **类别**: sql_injection
|
||||
- **代码**:
|
||||
```go
|
||||
query := "SELECT * FROM users WHERE username = '" + username + "'"
|
||||
```
|
||||
- **描述**: 直接拼接用户输入到 SQL 语句,存在 SQL 注入风险
|
||||
- **影响**: 攻击者可以通过构造恶意输入访问或篡改数据库
|
||||
- **修复建议**: 使用参数化查询或 ORM
|
||||
- **参考**:
|
||||
- [OWASP SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection)
|
||||
- [SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)
|
||||
|
||||
#### 2. 敏感信息泄露
|
||||
- **文件**: `config/database.go:10`
|
||||
- **风险等级**: 🔴 HIGH
|
||||
- **类别**: sensitive_data
|
||||
- **代码**:
|
||||
```go
|
||||
const DB_PASSWORD = "admin123"
|
||||
```
|
||||
- **描述**: 代码中硬编码数据库密码
|
||||
- **影响**: 敏感信息可能被泄露,导致数据库被攻击
|
||||
- **修复建议**: 使用环境变量或配置管理工具存储敏感信息
|
||||
|
||||
### ⚠️ 中危漏洞(3)
|
||||
|
||||
#### 1. 输入验证缺失
|
||||
- **文件**: `src/api/user.go:78`
|
||||
- **风险等级**: ⚠️ MEDIUM
|
||||
- **修复建议**: 添加用户名和密码的格式验证
|
||||
|
||||
### 📊 漏洞统计
|
||||
|
||||
- 总计: 5 个漏洞
|
||||
- 🔴 高危: 2 个
|
||||
- ⚠️ 中危: 3 个
|
||||
- ℹ️ 低危: 0 个
|
||||
|
||||
### 📝 安全建议
|
||||
|
||||
1. **立即修复**:修复所有高危漏洞,特别是 SQL 注入和敏感信息泄露
|
||||
2. **加强验证**:对所有用户输入进行严格的格式和长度验证
|
||||
3. **使用工具**:集成静态安全分析工具(如 SonarQube、Snyk)
|
||||
4. **定期审计**:定期进行安全代码审查
|
||||
|
||||
### 📚 参考资源
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
|
||||
- [CWE Top 25](https://cwe.mitre.org/top25/)
|
||||
```
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 1. 防御 SQL 注入
|
||||
|
||||
```go
|
||||
// ✅ 使用参数化查询
|
||||
stmt, err := db.Prepare("SELECT * FROM users WHERE username = ?")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
rows, err := stmt.Query(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// ✅ 使用 ORM
|
||||
var user User
|
||||
result := db.Where("username = ?", username).First(&user)
|
||||
```
|
||||
|
||||
### 2. 防御 XSS 攻击
|
||||
|
||||
```javascript
|
||||
// ✅ 使用 DOMPurify 库
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const clean = DOMPurify.sanitize(userInput);
|
||||
div.innerHTML = clean;
|
||||
|
||||
// ✅ 使用 CSP
|
||||
// 在 HTML 头中添加 CSP
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
```
|
||||
|
||||
### 3. 保护敏感信息
|
||||
|
||||
```go
|
||||
// ✅ 使用环境变量
|
||||
dbPassword := os.Getenv("DB_PASSWORD")
|
||||
|
||||
// ✅ 使用配置文件(加密)
|
||||
config := loadConfig("config.enc")
|
||||
|
||||
// ✅ 使用密钥管理服务
|
||||
secret := vault.GetSecret("database_password")
|
||||
```
|
||||
|
||||
## 🔍 常见问题
|
||||
|
||||
### Q: 如何确定漏洞的风险等级?
|
||||
|
||||
**A**: 综合考虑以下因素:
|
||||
- **利用难度**: 容易利用的漏洞风险更高
|
||||
- **影响范围**: 影响范围大的漏洞风险更高
|
||||
- **数据敏感性**: 涉及敏感数据的漏洞风险更高
|
||||
- **业务影响**: 对业务影响大的漏洞风险更高
|
||||
|
||||
### Q: 如何处理误报?
|
||||
|
||||
**A**:
|
||||
1. 审查代码上下文,确认是否真的存在安全风险
|
||||
2. 如果是误报,添加注释说明为什么是安全的
|
||||
3. 可以配置白名单忽略特定规则
|
||||
4. 提供反馈改进安全检查规则
|
||||
|
||||
### Q: 安全审查如何与 CI/CD 集成?
|
||||
|
||||
**A**:
|
||||
1. 在 CI 流程中添加安全扫描步骤
|
||||
2. 设置安全门禁(如不允许高危漏洞合并)
|
||||
3. 定期生成安全报告
|
||||
4. 集成 SAST 工具(如 SonarQube、Snyk)
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/) - Web 应用安全风险
|
||||
- [代码质量检查](code-review-quality.md) - 代码质量分析
|
||||
- [性能检查](code-review-performance.md) - 性能分析
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,682 @@
|
|||
# gitlink-code-review Skill 测试指南
|
||||
|
||||
## 📋 测试概述
|
||||
|
||||
本文档提供完整的测试指南,帮助你验证 gitlink-code-review Skill 的功能完整性、AI Agent 集成和实际可用性。
|
||||
|
||||
## 🎯 测试目标
|
||||
|
||||
1. **功能验证**: 确保所有功能按预期工作
|
||||
2. **AI 集成测试**: 验证 AI Agent 可以正确使用此 Skill
|
||||
3. **文档验证**: 确保文档完整且易于理解
|
||||
4. **实用验证**: 确保在实际场景中可用
|
||||
|
||||
## 🔧 前置条件
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
```bash
|
||||
# 确认 gitlink-cli 已安装
|
||||
gitlink-cli --version
|
||||
|
||||
# 确认已认证
|
||||
gitlink-cli auth status
|
||||
|
||||
# 如果未认证,执行登录
|
||||
gitlink-cli auth login
|
||||
```
|
||||
|
||||
### 2. 准备测试 PR
|
||||
|
||||
需要一个测试用的 PR,可以是:
|
||||
- 真实项目中的 PR
|
||||
- 自己创建的测试 PR
|
||||
- 公开项目的 PR
|
||||
|
||||
```bash
|
||||
# 查看可用的 PR
|
||||
gitlink-cli pr +list --owner <owner> --repo <repo> --format json
|
||||
```
|
||||
|
||||
## 📊 测试计划
|
||||
|
||||
### 测试级别
|
||||
|
||||
| 级别 | 测试内容 | 优先级 |
|
||||
|------|---------|--------|
|
||||
| Level 1 | 文档结构验证 | P0 |
|
||||
| Level 2 | 基础功能测试 | P0 |
|
||||
| Level 3 | AI Agent 集成测试 | P0 |
|
||||
| Level 4 | 完整工作流测试 | P1 |
|
||||
| Level 5 | 边界情况测试 | P2 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Level 1: 文档结构验证
|
||||
|
||||
### 测试 1.1: 检查必需文件存在
|
||||
|
||||
**目的**: 确保所有必需的文档文件都存在。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
cd skills/gitlink-code-review
|
||||
|
||||
# 检查必需文件
|
||||
ls -la SKILL.md
|
||||
ls -la README.md
|
||||
ls -la REFERENCE.md
|
||||
|
||||
# 检查目录结构
|
||||
ls -la references/
|
||||
ls -la examples/
|
||||
|
||||
# 验证文件内容
|
||||
wc -l SKILL.md
|
||||
wc -l README.md
|
||||
wc -l REFERENCE.md
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- ✅ SKILL.md 存在且 >100 行
|
||||
- ✅ README.md 存在且 >100 行
|
||||
- ✅ REFERENCE.md 存在且 >200 行
|
||||
- ✅ references/ 目录包含至少 3 个 .md 文件
|
||||
- ✅ examples/ 目录包含至少 3 个 .md 文件
|
||||
|
||||
### 测试 1.2: 验证 Frontmatter 格式
|
||||
|
||||
**目的**: 确保 SKILL.md 的 frontmatter 符合规范。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 查看 SKILL.md 的前 20 行
|
||||
head -20 SKILL.md
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
```yaml
|
||||
---
|
||||
name: gitlink-code-review
|
||||
version: 1.0.0
|
||||
description: "智能代码审查:..."
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli pr --help"
|
||||
---
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 包含 `name` 字段
|
||||
- ✅ 包含 `version` 字段
|
||||
- ✅ 包含 `description` 字段
|
||||
- ✅ 包含 `metadata` 字段
|
||||
- ✅ `metadata.requires.bins` 包含 `gitlink-cli`
|
||||
|
||||
### 测试 1.3: 验证文档引用
|
||||
|
||||
**目的**: 确保文档之间的相互引用正确。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 检查 SKILL.md 中的引用
|
||||
grep -n "\[.*\](.*.md)" SKILL.md
|
||||
|
||||
# 检查 README.md 中的引用
|
||||
grep -n "\[.*\](.*.md)" README.md
|
||||
|
||||
# 验证引用的文件是否存在
|
||||
# [手动检查引用的文件路径是否正确]
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 所有引用的文件都存在
|
||||
- ✅ 引用路径正确
|
||||
- ✅ 没有断开的链接
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Level 2: 基础功能测试
|
||||
|
||||
### 测试 2.1: 验证 gitlink-cli PR 命令
|
||||
|
||||
**目的**: 确保依赖的 gitlink-cli 命令正常工作。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 设置测试变量
|
||||
OWNER="Gitlink"
|
||||
REPO="forgeplus"
|
||||
PR_ID=<一个真实的PR编号>
|
||||
|
||||
# 测试 pr +view 命令
|
||||
echo "=== 测试 pr +view ==="
|
||||
gitlink-cli pr +view --id $PR_ID --format json > test_pr_view.json
|
||||
cat test_pr_view.json | jq '.ok'
|
||||
|
||||
# 测试 pr +files 命令
|
||||
echo "=== 测试 pr +files ==="
|
||||
gitlink-cli pr +files --id $PR_ID --format json > test_pr_files.json
|
||||
cat test_pr_files.json | jq '.ok'
|
||||
|
||||
# 测试 pr +diff 命令
|
||||
echo "=== 测试 pr +diff ==="
|
||||
gitlink-cli pr +diff --id $PR_ID --format json > test_pr_diff.json
|
||||
cat test_pr_diff.json | jq '.ok'
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- ✅ `pr +view` 返回 `{"ok": true}`
|
||||
- ✅ `pr +files` 返回 `{"ok": true}`
|
||||
- ✅ `pr +diff` 返回 `{"ok": true}`
|
||||
- ✅ JSON 文件包含有效的数据
|
||||
|
||||
### 测试 2.2: 验证数据解析
|
||||
|
||||
**目的**: 确保能够正确解析 gitlink-cli 返回的数据。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 验证 PR 数据结构
|
||||
echo "=== 验证 PR 详情 ==="
|
||||
cat test_pr_view.json | jq '.data | keys'
|
||||
# 应包含: id, title, author, status, etc.
|
||||
|
||||
echo "=== 验证文件列表 ==="
|
||||
cat test_pr_files.json | jq '.data.files | length'
|
||||
# 应该 > 0
|
||||
|
||||
echo "=== 验证 diff 内容 ==="
|
||||
cat test_pr_diff.json | jq '.data.diff' | head -c 100
|
||||
# 应该包含 diff 内容
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- ✅ PR 详情包含必要的字段
|
||||
- ✅ 文件列表非空
|
||||
- ✅ diff 内容存在
|
||||
|
||||
### 测试 2.3: 手动代码审查模拟
|
||||
|
||||
**目的**: 手动执行一次完整的代码审查流程。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 1. 获取 PR 信息
|
||||
echo "步骤 1: 获取 PR 信息"
|
||||
gitlink-cli pr +view --id $PR_ID --format json | jq '{id: .data.id, title: .data.title, author: .data.author.login}'
|
||||
|
||||
# 2. 获取文件列表
|
||||
echo "步骤 2: 获取文件列表"
|
||||
gitlink-cli pr +files --id $PR_ID --format json | jq '.data.files[] | {filename: .filename, changes: .changes}'
|
||||
|
||||
# 3. 获取 diff
|
||||
echo "步骤 3: 获取 diff"
|
||||
gitlink-cli pr +diff --id $PR_ID --format json | jq -r '.data.diff' | head -50
|
||||
|
||||
# 4. 手动分析代码(需要人工查看)
|
||||
echo "步骤 4: 手动分析代码"
|
||||
echo "请查看上面的代码变更,识别潜在问题"
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 每个步骤都能成功执行
|
||||
- ✅ 数据格式正确
|
||||
- ✅ 可以看到代码变更内容
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Level 3: AI Agent 集成测试
|
||||
|
||||
### 测试 3.1: Claude Code 基础测试
|
||||
|
||||
**目的**: 验证 Claude Code 可以识别和使用此 Skill。
|
||||
|
||||
**在 Claude Code 中执行**:
|
||||
|
||||
```
|
||||
用户: 我需要审查一个 PR,PR 编号是 123
|
||||
|
||||
[预期行为]:
|
||||
1. Claude Code 应该识别需要使用 gitlink-code-review Skill
|
||||
2. 自动读取 SKILL.md 了解如何操作
|
||||
3. 执行正确的命令序列
|
||||
4. 生成审查报告
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ AI 识别到需要使用 gitlink-code-review Skill
|
||||
- ✅ AI 执行了 `pr +view`, `pr +files`, `pr +diff` 命令
|
||||
- ✅ AI 生成了结构化的审查报告
|
||||
- ✅ 提供了可操作的建议
|
||||
|
||||
### 测试 3.2: Claude Code 场景测试
|
||||
|
||||
**场景 1: 基础审查**
|
||||
|
||||
```
|
||||
用户: 审查 PR #123
|
||||
|
||||
[预期输出]:
|
||||
- 获取 PR 信息
|
||||
- 分析代码变更
|
||||
- 生成审查报告
|
||||
- 提供改进建议
|
||||
```
|
||||
|
||||
**场景 2: 重点安全审查**
|
||||
|
||||
```
|
||||
用户: 审查 PR #456,重点关注安全问题
|
||||
|
||||
[预期输出]:
|
||||
- 获取 PR 信息
|
||||
- 重点分析安全问题
|
||||
- 列出发现的安全漏洞
|
||||
- 提供修复建议
|
||||
```
|
||||
|
||||
**场景 3: 自动添加评论**
|
||||
|
||||
```
|
||||
用户: 审查 PR #789 并添加评论到 PR
|
||||
|
||||
[预期输出]:
|
||||
- 获取 PR 信息
|
||||
- 分析代码
|
||||
- 生成报告
|
||||
- 添加评论到 PR
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ AI 根据用户请求调整审查重点
|
||||
- ✅ AI 正确执行相应的命令
|
||||
- ✅ 输出格式符合预期
|
||||
- ✅ 提供了有价值的建议
|
||||
|
||||
### 测试 3.3: 提示词测试
|
||||
|
||||
**目的**: 验证 Skill 中的提示词是否有效。
|
||||
|
||||
**测试提示词**:
|
||||
```
|
||||
请分析以下 PR 的代码变更,检查代码质量、安全性和性能问题。
|
||||
|
||||
PR 数据:
|
||||
[粘贴 test_pr_view.json, test_pr_files.json, test_pr_diff.json 的内容]
|
||||
|
||||
请以 JSON 格式输出审查报告,包含:
|
||||
- overall_assessment: 总体评估
|
||||
- issues: 问题列表
|
||||
- positive_notes: 优秀实践
|
||||
- recommendations: 改进建议
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ AI 理解任务要求
|
||||
- ✅ AI 分析代码变更
|
||||
- ✅ 输出格式符合要求
|
||||
- ✅ 发现了真实的问题
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Level 4: 完整工作流测试
|
||||
|
||||
### 测试 4.1: 基础审查工作流
|
||||
|
||||
**目的**: 验证 `basic-review-workflow.md` 中的工作流。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 按照基础审查工作流执行
|
||||
PR_ID=<测试PR编号>
|
||||
|
||||
# 步骤 1: 获取 PR 详情
|
||||
gitlink-cli pr +view --id $PR_ID --format json
|
||||
|
||||
# 步骤 2: 获取变更文件列表
|
||||
gitlink-cli pr +files --id $PR_ID --format json
|
||||
|
||||
# 步骤 3: 获取 diff 内容
|
||||
gitlink-cli pr +diff --id $PR_ID --format json
|
||||
|
||||
# 步骤 4: 浏览代码变更
|
||||
gitlink-cli pr +diff --id $PR_ID --format json | jq -r '.data.diff' | less
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 所有步骤都能成功执行
|
||||
- ✅ 数据格式正确
|
||||
- ✅ 可以看到代码变更
|
||||
|
||||
### 测试 4.2: 全面审查工作流
|
||||
|
||||
**目的**: 验证 `comprehensive-review-workflow.md` 中的工作流。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 按照全面审查工作流执行
|
||||
PR_ID=<测试PR编号>
|
||||
|
||||
# 1. 获取数据
|
||||
gitlink-cli pr +view --id $PR_ID --format json > pr_info.json
|
||||
gitlink-cli pr +files --id $PR_ID --format json > pr_files.json
|
||||
gitlink-cli pr +diff --id $PR_ID --format json > pr_diff.json
|
||||
|
||||
# 2. 数据预处理
|
||||
cat pr_files.json | jq '.data.files | map(select(.changes > 10))' > main_changes.json
|
||||
|
||||
# 3. 组织分析数据
|
||||
cat > analysis_input.json <<EOF
|
||||
{
|
||||
"pr_info": $(cat pr_info.json | jq '.data'),
|
||||
"files": $(cat pr_files.json | jq '.data.files'),
|
||||
"diff": $(cat pr_diff.json | jq -r '.data.diff')
|
||||
}
|
||||
EOF
|
||||
|
||||
# 4. 验证数据
|
||||
cat analysis_input.json | jq '.pr_info.title'
|
||||
cat analysis_input.json | jq '.files | length'
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 数据预处理成功
|
||||
- ✅ 分析数据格式正确
|
||||
- ✅ 包含所有必要的信息
|
||||
|
||||
### 测试 4.3: 自动审查工作流
|
||||
|
||||
**目的**: 验证 `auto-review-pr.md` 中的自动化流程。
|
||||
|
||||
**在 Claude Code 中测试**:
|
||||
|
||||
```
|
||||
用户: 使用自动审查模式审查 PR #123
|
||||
|
||||
[预期行为]:
|
||||
1. AI 自动获取所有必要数据
|
||||
2. AI 自动分析代码
|
||||
3. AI 自动生成报告
|
||||
4. AI 询问是否添加评论
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 流程完全自动化
|
||||
- ✅ 不需要人工干预
|
||||
- ✅ 生成完整的报告
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Level 5: 边界情况测试
|
||||
|
||||
### 测试 5.1: 大型 PR 测试
|
||||
|
||||
**目的**: 测试对大型 PR 的处理能力。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 查找一个大型 PR(>500 行变更)
|
||||
gitlink-cli pr +list --format json | \
|
||||
jq '.data[] | select(.additions > 500) | {id: .id, additions: .additions}'
|
||||
|
||||
# 测试获取 diff
|
||||
gitlink-cli pr +diff --id <大型PR编号> --format json | \
|
||||
jq '.data | length'
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 能够处理大型 diff
|
||||
- ✅ 不会超时或崩溃
|
||||
- ✅ 输出格式正确
|
||||
|
||||
### 测试 5.2: 错误处理测试
|
||||
|
||||
**目的**: 测试错误情况的处理。
|
||||
|
||||
**测试不存在的 PR**:
|
||||
```bash
|
||||
gitlink-cli pr +view --id 999999 --format json
|
||||
# 应该返回错误信息
|
||||
```
|
||||
|
||||
**测试无权限的 PR**:
|
||||
```bash
|
||||
gitlink-cli pr +view --id <私有PR编号> --format json
|
||||
# 应该返回 403 错误
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 错误信息清晰
|
||||
- ✅ 包含错误原因
|
||||
- ✅ 提供解决建议
|
||||
|
||||
### 测试 5.3: 不同文件类型测试
|
||||
|
||||
**目的**: 测试对不同文件类型的处理。
|
||||
|
||||
**步骤**:
|
||||
```bash
|
||||
# 查找包含不同文件类型的 PR
|
||||
# Go 文件
|
||||
gitlink-cli pr +files --id $PR_ID --format json | \
|
||||
jq '.data.files[] | select(.filename | endswith(".go"))'
|
||||
|
||||
# JavaScript 文件
|
||||
gitlink-cli pr +files --id $PR_ID --format json | \
|
||||
jq '.data.files[] | select(.filename | endswith(".js"))'
|
||||
|
||||
# Python 文件
|
||||
gitlink-cli pr +files --id $PR_ID --format json | \
|
||||
jq '.data.files[] | select(.filename | endswith(".py"))'
|
||||
```
|
||||
|
||||
**验证点**:
|
||||
- ✅ 能够识别不同语言
|
||||
- ✅ 能够针对性分析
|
||||
- ✅ 建议符合语言特性
|
||||
|
||||
---
|
||||
|
||||
## 📊 测试报告模板
|
||||
|
||||
### 测试执行记录
|
||||
|
||||
```markdown
|
||||
# gitlink-code-review Skill 测试报告
|
||||
|
||||
**测试日期**: 2026-06-12
|
||||
**测试人员**: [姓名]
|
||||
**测试环境**: [环境描述]
|
||||
|
||||
## 测试结果总览
|
||||
|
||||
| 测试级别 | 通过/总数 | 状态 |
|
||||
|---------|----------|------|
|
||||
| Level 1 | ?/? | ⏳ |
|
||||
| Level 2 | ?/? | ⏳ |
|
||||
| Level 3 | ?/? | ⏳ |
|
||||
| Level 4 | ?/? | ⏳ |
|
||||
| Level 5 | ?/? | ⏳ |
|
||||
|
||||
## 详细测试结果
|
||||
|
||||
### Level 1: 文档结构验证
|
||||
|
||||
- [ ] 测试 1.1: 检查必需文件存在 - ⏳
|
||||
- [ ] 测试 1.2: 验证 Frontmatter 格式 - ⏳
|
||||
- [ ] 测试 1.3: 验证文档引用 - ⏳
|
||||
|
||||
### Level 2: 基础功能测试
|
||||
|
||||
- [ ] 测试 2.1: 验证 gitlink-cli PR 命令 - ⏳
|
||||
- [ ] 测试 2.2: 验证数据解析 - ⏳
|
||||
- [ ] 测试 2.3: 手动代码审查模拟 - ⏳
|
||||
|
||||
### Level 3: AI Agent 集成测试
|
||||
|
||||
- [ ] 测试 3.1: Claude Code 基础测试 - ⏳
|
||||
- [ ] 测试 3.2: Claude Code 场景测试 - ⏳
|
||||
- [ ] 测试 3.3: 提示词测试 - ⏳
|
||||
|
||||
### Level 4: 完整工作流测试
|
||||
|
||||
- [ ] 测试 4.1: 基础审查工作流 - ⏳
|
||||
- [ ] 测试 4.2: 全面审查工作流 - ⏳
|
||||
- [ ] 测试 4.3: 自动审查工作流 - ⏳
|
||||
|
||||
### Level 5: 边界情况测试
|
||||
|
||||
- [ ] 测试 5.1: 大型 PR 测试 - ⏳
|
||||
- [ ] 测试 5.2: 错误处理测试 - ⏳
|
||||
- [ ] 测试 5.3: 不同文件类型测试 - ⏳
|
||||
|
||||
## 发现的问题
|
||||
|
||||
### 问题 1
|
||||
- **描述**: [问题描述]
|
||||
- **严重性**: [高/中/低]
|
||||
- **状态**: [待修复/已修复]
|
||||
|
||||
## 建议和改进
|
||||
|
||||
### 建议 1
|
||||
- **描述**: [建议描述]
|
||||
- **优先级**: [高/中/低]
|
||||
|
||||
## 总结
|
||||
|
||||
**总体评估**: [通过/不通过]
|
||||
**评分**: [?/100]
|
||||
**建议**: [是否建议投入使用]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 快速测试脚本
|
||||
|
||||
为了快速验证 Skill 的基本功能,可以使用以下脚本:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# quick-test.sh - 快速测试脚本
|
||||
|
||||
set -e
|
||||
|
||||
echo "=== gitlink-code-review Skill 快速测试 ==="
|
||||
|
||||
# 配置
|
||||
PR_ID=${1:-<默认PR编号>}
|
||||
OWNER=${2:-Gitlink}
|
||||
REPO=${3:-forgeplus}
|
||||
|
||||
echo "测试 PR: $PR_ID"
|
||||
echo ""
|
||||
|
||||
# Level 1: 文档检查
|
||||
echo "Level 1: 检查文档..."
|
||||
if [ -f "SKILL.md" ] && [ -f "README.md" ] && [ -f "REFERENCE.md" ]; then
|
||||
echo "✅ 文档文件存在"
|
||||
else
|
||||
echo "❌ 缺少必需文档"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Level 2: 功能测试
|
||||
echo ""
|
||||
echo "Level 2: 测试 gitlink-cli 命令..."
|
||||
|
||||
# 测试 pr +view
|
||||
if gitlink-cli pr +view --id $PR_ID --format json | jq -e '.ok == true' > /dev/null; then
|
||||
echo "✅ pr +view 正常"
|
||||
else
|
||||
echo "❌ pr +view 失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试 pr +files
|
||||
if gitlink-cli pr +files --id $PR_ID --format json | jq -e '.ok == true' > /dev/null; then
|
||||
echo "✅ pr +files 正常"
|
||||
else
|
||||
echo "❌ pr +files 失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 测试 pr +diff
|
||||
if gitlink-cli pr +diff --id $PR_ID --format json | jq -e '.ok == true' > /dev/null; then
|
||||
echo "✅ pr +diff 正常"
|
||||
else
|
||||
echo "❌ pr +diff 失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== 快速测试完成 ==="
|
||||
echo "✅ 所有基础测试通过"
|
||||
echo ""
|
||||
echo "下一步:"
|
||||
echo "1. 在 Claude Code 中测试 AI 集成"
|
||||
echo "2. 执行完整工作流测试"
|
||||
echo "3. 验证边界情况"
|
||||
```
|
||||
|
||||
**使用方法**:
|
||||
```bash
|
||||
chmod +x quick-test.sh
|
||||
./quick-test.sh <PR编号>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如果测试过程中遇到问题:
|
||||
|
||||
1. **查看文档**
|
||||
- [SKILL.md](SKILL.md) - 技能总览
|
||||
- [README.md](README.md) - 使用说明
|
||||
- [REFERENCE.md](REFERENCE.md) - API 参考
|
||||
|
||||
2. **检查配置**
|
||||
```bash
|
||||
# 检查 gitlink-cli 版本
|
||||
gitlink-cli --version
|
||||
|
||||
# 检查认证状态
|
||||
gitlink-cli auth status
|
||||
```
|
||||
|
||||
3. **查看错误日志**
|
||||
```bash
|
||||
# 启用调试模式
|
||||
gitlink-cli pr +view --id $PR_ID --format json --debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 测试最佳实践
|
||||
|
||||
### 1. 渐进式测试
|
||||
|
||||
- 从 Level 1 开始,逐步升级
|
||||
- 每个级别通过后再进行下一级
|
||||
- 记录每个测试的结果
|
||||
|
||||
### 2. 真实场景测试
|
||||
|
||||
- 使用真实的 PR 进行测试
|
||||
- 覆盖不同类型的 PR(功能、修复、重构)
|
||||
- 测试不同大小的 PR
|
||||
|
||||
### 3. 持续改进
|
||||
|
||||
- 记录发现的问题
|
||||
- 及时修复和改进
|
||||
- 定期重新测试
|
||||
|
||||
---
|
||||
|
||||
**测试完成后,请填写测试报告并评估 Skill 是否可以投入使用。**
|
||||
|
||||
*最后更新: 2026-06-12*
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
---
|
||||
name: gitlink-faq
|
||||
version: 1.3.0
|
||||
description: "Issue 知识库:从项目 Issue 自动分类(Bug/功能请求/使用问题),按类型归纳聚类,生成结构化知识库发布到 Wiki;增量更新已有知识库;检测新 Issue 是否与已有问题重复。当用户需要整理 Issue、归纳 Issue、总结常见问题/Bug、建立知识库、更新知识库、检查重复 Issue、查重时触发。通用 Issue 操作(创建/查看/更新/关闭/评论等)请使用 gitlink-issue skill。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli issue --help"
|
||||
---
|
||||
|
||||
# gitlink-faq(Issue 知识库)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 写入/删除操作前,务必先确认用户意图。默认 dry-run 预览,用户确认后再执行。**
|
||||
**CRITICAL — 只读操作(`issue +list`、`issue +view`、`wiki +view`)自动连续执行,不要逐个请求用户确认。数据采集和分析阶段一气呵成,仅在最终写入步骤前暂停确认。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)
|
||||
|
||||
## 运行模式
|
||||
|
||||
| 模式 | 说明 | 典型触发语 | 需要认证 |
|
||||
|------|------|------------|----------|
|
||||
| **模式 A:Issue 归纳** | 采集全部 Issue → 按类型分类 → 主题聚类 → 生成结构化知识库发布到 Wiki | "整理 Issue""归纳关闭的 Issue""总结项目问题""建立知识库""分析 Issue" | 是(发布 Wiki 需写入) |
|
||||
| **模式 B:重复检测** | 对指定 Issue,在知识库和历史 Issue 中查找相似项,判断是否重复 | "查重""有没有类似的 Issue""这个是不是有人报过" | 否(仅读取;评论需认证) |
|
||||
| **模式 C:Wiki 增量更新** | 读取已有 Wiki 页面 → 拉取新 Issue → 分类合并到现有知识库结构 → 更新 Wiki | "把这个 Issue 加到知识库""更新 Wiki 页面""补充到知识库里""同步最新 Issue 到 Wiki" | 是(读取+写入 Wiki) |
|
||||
|
||||
---
|
||||
|
||||
## 模式 A:Issue 归纳(6 步)
|
||||
|
||||
当用户说 **"整理 Issue"、"归纳已关闭 Issue"、"总结项目问题"、"建立知识库"** 等时,执行以下流程。
|
||||
|
||||
### 第 1 步:采集全部 Issue
|
||||
|
||||
**CRITICAL**:不要仅采集 `--state closed`。GitLink 平台很多已解决的 Issue 不会被及时设置为"关闭"状态,只看 closed 会漏掉大量有分析价值的 Issue。
|
||||
|
||||
```bash
|
||||
# 同时采集 open 和 closed,覆盖所有 Issue
|
||||
gitlink-cli issue +list --state open --limit 100 --format json
|
||||
gitlink-cli issue +list --state closed --limit 100 --format json
|
||||
```
|
||||
|
||||
将两份列表合并去重,得到完整 Issue 集合。
|
||||
|
||||
采集量策略参见 [`references/gitlink-faq-collect.md`](references/gitlink-faq-collect.md)。
|
||||
|
||||
### 第 2 步:筛选 + 读取详情(依靠 description)
|
||||
|
||||
先按标题粗筛,仅排除:
|
||||
- 标题含 `[test]` / `测试` 的纯测试 Issue
|
||||
- 标题为空或仅有占位符的 Issue
|
||||
|
||||
其余 Issue **一律保留**,逐个读详情:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --number N --format json
|
||||
```
|
||||
|
||||
提取字段:`subject`(标题)、`description`(描述)。
|
||||
|
||||
**description 是核心分析源**:
|
||||
- 部分 Issue 的 description 非常详细(含复现步骤、环境信息、修复建议)
|
||||
- description 的质量直接决定分析深度
|
||||
- `comment_journals_count` 数值可参考(表示讨论热度),但实际评论内容无法通过 API 获取(见下方 API 限制)
|
||||
|
||||
每批 20-30 个,尽量覆盖所有非测试 Issue。
|
||||
|
||||
### 第 3 步:Issue 类型分类
|
||||
|
||||
对每条 Issue,AI 根据 (subject + description) 判断类型。**不要因为"缺少 journals"或"状态未关闭"而排除 Issue**——是否有分析价值取决于内容本身,不是状态。
|
||||
|
||||
| 类型 | 判断依据 | 纳入条件 | 分析价值 |
|
||||
|------|----------|----------|----------|
|
||||
| **Bug 报告** | 描述异常行为、报错、与预期不符 | description 非空 或 subject 明确描述症状 | 高频 Bug = 模块质量信号 |
|
||||
| **功能请求** | 建议新增能力、改进体验 | 保留。高频请求反映用户需求 | 用户需求优先级 |
|
||||
| **使用问题** | 不知道怎么用、配置不清楚 | 保留。即使未回复也是需求信号 | 文档/体验改进方向 |
|
||||
| **其他** | 不属于以上三类 | 标题无实质内容则忽略 | 低 |
|
||||
|
||||
**输出**:每条 Issue 带上类型标签。
|
||||
|
||||
**宽松原则**:宁可多留一条低价值的,也别漏掉一条有洞察的。不确定类型的归入"其他"而非丢弃。
|
||||
|
||||
### 第 4 步:按类型分别聚类
|
||||
|
||||
将同类型的 Issue 按语义相似度聚类。不同类型聚类维度不同:
|
||||
|
||||
| 类型 | 聚类维度 | 聚类目标 |
|
||||
|------|----------|----------|
|
||||
| Bug 报告 | 按**出问题的模块/功能**归类 | 找出"哪个模块 Bug 最多"、"同类 Bug 的共同根因" |
|
||||
| 功能请求 | 按**请求的功能领域**归类 | 找出"用户最想要什么能力"、"哪些增强呼声最高" |
|
||||
| 使用问题 | 按**操作场景**归类 | 传统 Q&A:提炼"问题 → 答案" |
|
||||
|
||||
**Bug 聚类输出**:
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "Issue 数据显示",
|
||||
"bug_count": 4,
|
||||
"pattern": "CLI 返回数据与网页端不一致、字段缺失",
|
||||
"affected_issues": [5, 7, 15, 18],
|
||||
"typical_symptom": "issue +view / pr +view 返回结果缺少关键字段或与网页端不一致"
|
||||
}
|
||||
```
|
||||
|
||||
**功能请求聚类输出**:
|
||||
|
||||
```json
|
||||
{
|
||||
"feature_area": "API 能力增强",
|
||||
"request_count": 3,
|
||||
"pattern": "希望 API 支持更多查询/操作能力",
|
||||
"affected_issues": [9, 14, 21],
|
||||
"common_ask": "支持按序号查询 Issue、读取仓库文件、返回完整时间字段"
|
||||
}
|
||||
```
|
||||
|
||||
**使用问题聚类输出**(仅当同类 ≥2 条时生成 Q&A):
|
||||
|
||||
```json
|
||||
{
|
||||
"topic": "安装配置",
|
||||
"question": "gitlink-cli 安装后无法运行怎么办?",
|
||||
"answer": "检查 PATH、确认平台支持,详见安装文档",
|
||||
"source_issues": [16, 20]
|
||||
}
|
||||
```
|
||||
|
||||
### 第 5 步:生成知识库文档
|
||||
|
||||
按以下结构组织 Markdown:
|
||||
|
||||
```markdown
|
||||
# 📊 Issue 知识库
|
||||
|
||||
> 自动生成 | 数据来源:已关闭 Issue({N} 条)
|
||||
> 更新时间:{DATE}
|
||||
|
||||
## 🐛 Bug 高频模块
|
||||
|
||||
### {模块名}({N} 个 Bug)
|
||||
- **典型症状**: ...
|
||||
- **涉及 Issue**: #A, #B, #C
|
||||
- **已知修复**: ...(如有)
|
||||
|
||||
## 💡 功能请求热度
|
||||
|
||||
### {功能领域}({N} 个请求)
|
||||
- **用户期望**: ...
|
||||
- **涉及 Issue**: #D, #E, #F
|
||||
|
||||
## 📖 常见使用问题
|
||||
|
||||
### Q: {问题}?
|
||||
**A:** {答案}
|
||||
> 来源: #G, #H
|
||||
```
|
||||
|
||||
根据实际数据量,若某类型 Issue 过少(<2 条),该章节可省略或合并到"其他"。
|
||||
|
||||
文档模板参见 [`examples/faq-template.md`](examples/faq-template.md)。
|
||||
|
||||
完成后保存为 `./issue-knowledge-base.md`,展示给用户预览。
|
||||
|
||||
### 第 6 步:发布到 Wiki
|
||||
|
||||
用户确认后:
|
||||
|
||||
```bash
|
||||
# 首次创建
|
||||
gitlink-cli wiki +create --title "Issue-知识库" --file ./issue-knowledge-base.md
|
||||
|
||||
# 后续更新
|
||||
gitlink-cli wiki +update --title "Issue-知识库" --file ./issue-knowledge-base.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模式 B:Issue 查找与查重
|
||||
|
||||
当用户说 **"查重"、"检查重复"、"有没有类似的 Issue"、"找一下关于 xx 的 Issue"、"这个是不是有人报过"、"有没有和 xx 相关的"** 时执行。
|
||||
|
||||
**CRITICAL**:本模式下,匹配到候选 Issue 后**必须自动读取其详情和 journals**,不要问用户"需要我读详情吗"。一次性完成搜索→读详情→给出分析结论。
|
||||
|
||||
### 第 1 步:确定搜索目标
|
||||
|
||||
- 用户指定了 Issue 编号 → `issue +view --number N` 获取目标内容
|
||||
- 用户描述了主题/关键词(如"与创建 Issue 有关的")→ 进入关键词搜索模式
|
||||
|
||||
### 第 2 步:拉取 Issue 列表
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list --state open --limit 100 --format json
|
||||
gitlink-cli issue +list --state closed --limit 100 --format json
|
||||
```
|
||||
|
||||
提取全部 Issue 的 `subject`(标题),按用户主题进行标题匹配。
|
||||
|
||||
### 第 3 步:自动读取候选 Issue 详情(关键步骤)
|
||||
|
||||
筛选出候选 Issue 后,**立即逐个读取详情,不需询问用户**:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --number N --format json
|
||||
```
|
||||
|
||||
提取:标题、描述、journals(评论讨论历史)。
|
||||
|
||||
### 第 4 步:给出分析结论
|
||||
|
||||
综合标题+描述+journals,给用户完整分析:
|
||||
|
||||
- **直接匹配**:Issue 的核心讨论内容、维护者回复中有无解决方案
|
||||
- **间接相关**:Issue 涉及同一模块/功能但由于不同原因
|
||||
- **不相关**:标题含关键词但内容无关
|
||||
|
||||
对每条匹配的 Issue 输出:
|
||||
- 标题 + 编号
|
||||
- 一句话摘要(从描述和 journals 提取)
|
||||
- 维护者有无回复/解决方案
|
||||
- 相关度判定
|
||||
|
||||
### 第 5 步:执行操作(仅查重+用户确认后)
|
||||
|
||||
如果是查重场景且判定高度重复,用户确认后执行:
|
||||
|
||||
```bash
|
||||
# 添加评论(使用 Issue ID,参见 gitlink-issue skill)
|
||||
gitlink-cli issue +comment --number N --body "此 Issue 与 #M 内容重复,建议..."
|
||||
|
||||
# 打重复标签(使用项目内编号,批量操作)
|
||||
gitlink-cli issue +batch-label --label duplicate --numbers N,M
|
||||
```
|
||||
|
||||
> 通用 Issue 操作(创建、查看、更新、关闭、评论等)参见 [`../gitlink-issue/SKILL.md`](../gitlink-issue/SKILL.md)。
|
||||
|
||||
---
|
||||
|
||||
## 模式 C:Wiki 增量更新
|
||||
|
||||
当用户说 **"把这个 Issue 加到知识库里"、"更新 Wiki 页面"、"补充到知识库"、"同步最新 Issue 到 Wiki"** 等时执行。
|
||||
|
||||
**核心思路**:不是重新生成整个知识库,而是读取现有 Wiki 内容 → 拉取新 Issue → 分类合并 → 更新 Wiki。
|
||||
|
||||
### 第 1 步:读取现有 Wiki
|
||||
|
||||
```bash
|
||||
gitlink-cli wiki +view --title "Issue-知识库" --format json
|
||||
```
|
||||
|
||||
从返回的 JSON 中提取内容(CLI 自动处理 base64 解码)。
|
||||
|
||||
如果 Wiki 不存在(404),回退到**模式 A**——首次创建知识库。
|
||||
|
||||
### 第 2 步:拉取用户指定的 Issue
|
||||
|
||||
根据用户指示直接读 Issue 详情,**用户说哪个就拉哪个**,不要自己去拉全量列表做差集对比。
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --number N --format json
|
||||
```
|
||||
|
||||
| 用户意图 | 操作 |
|
||||
|----------|------|
|
||||
| "把 Issue #N 加到知识库" | 只读 #N:`issue +view --number N` |
|
||||
| "把这几个 Issue 加进去:#A, #B, #C" | 逐个读 #A, #B, #C |
|
||||
| "把关于 XX 的 Issue 补充进去" | 先按关键词搜索(同模式 B 第 2-3 步),找到匹配 Issue 后逐个读详情 |
|
||||
| "把最近新增的 Issue 同步到 Wiki" | 用户未指定具体编号时,才拉全量列表,对比 Wiki 中已有的编号做差集 |
|
||||
|
||||
### 第 3 步:分类新 Issue
|
||||
|
||||
### 第 4 步:合并到现有知识库结构
|
||||
|
||||
将新 Issue 按类型归入现有章节:
|
||||
|
||||
- **Bug**:归入"Bug 高频模块",若属于已有模块则追加,否则新建模块条目
|
||||
- **功能请求**:归入"功能请求热度",若属于已有领域则合并,否则新建领域条目
|
||||
- **使用问题**:归入"常见使用问题",新建 Q&A 条目
|
||||
|
||||
合并时更新:
|
||||
- Issue 计数(总数、各类型数量)
|
||||
- 更新时间
|
||||
- 受影响的 `-- 涉及 Issue` 列表
|
||||
|
||||
### 第 5 步:生成合并后的文档并预览
|
||||
|
||||
将合并后的完整 Markdown 展示给用户预览,标注新增/变更的部分(可用 `[NEW]` 标记)。
|
||||
|
||||
### 第 6 步:更新 Wiki
|
||||
|
||||
用户确认后:
|
||||
|
||||
```bash
|
||||
gitlink-cli wiki +update --title "Issue-知识库" --file ./issue-knowledge-base.md
|
||||
```
|
||||
|
||||
**CRITICAL**:必须用 `wiki +update`(不是 `+create`),因为页面已存在。
|
||||
|
||||
---
|
||||
|
||||
## 命令速查
|
||||
|
||||
本 skill 只涉及知识库分析相关的命令。通用 Issue 操作(创建、查看、更新、关闭、批量操作等)统一使用 [`gitlink-issue`](../gitlink-issue/SKILL.md) skill。
|
||||
|
||||
| 命令 | 用途 | 模式 |
|
||||
|------|------|------|
|
||||
| `issue +list --state open --limit 100 --format json` | 获取 open Issue 列表 | A / C |
|
||||
| `issue +list --state closed --limit 100 --format json` | 获取 closed Issue 列表 | A / C |
|
||||
| `issue +view --number N --format json` | 读取单个 Issue 详情 | A / B / C |
|
||||
| `issue +comment --number N --body "..."` | 查重时添加评论引导用户 | B |
|
||||
| `issue +batch-label --label duplicate --numbers N,M` | 查重时批量标记重复 | B |
|
||||
| `wiki +view --title "Issue-知识库" --format json` | 查看已有知识库内容 | C |
|
||||
| `wiki +create --title "Issue-知识库" --file ./xxx.md` | 首次创建知识库 Wiki 页面 | A |
|
||||
| `wiki +update --title "Issue-知识库" --file ./xxx.md` | 增量更新知识库 Wiki 页面 | A / C |
|
||||
|
||||
---
|
||||
|
||||
## API 注意事项
|
||||
|
||||
- `issue +list` 的 `--state` 参数**不可靠**(与 PR 列表同款问题),返回列表可能包含所有状态。因此必须同时拉 open + closed 两份列表合并去重。
|
||||
- **`issue +view` 不返回 journals 内容**:只有 `comment_journals_count`(评论数量),无法通过 API 读取实际评论。`GET /v1/.../issues/{N}/journals` 返回 HTML 而非 JSON。分析只能依靠 `subject` + `description`。
|
||||
- **`issue +label-add` API 返回 404**:GitLink 平台 `POST /v1/.../issues/{N}/labels` 端点不可用。打标签请改用 `issue +batch-label`(走 `updateIssueField` 而非 labels API)。
|
||||
- `wiki +view` Gateway API 可能返回 404(已知问题),但 `wiki +create` / `wiki +update` 写入正常。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **先分类再聚类**:不同 Issue 类型不能混在一起聚类(Bug 和功能请求本质不同)
|
||||
- **所有结论必须有来源**:Bug 模式、功能热度、Q&A 答案都必须来自实际 Issue,不编造
|
||||
- **数据不足时如实说明**:某类型 < 2 条时不强行归纳,标注"暂无足够数据"
|
||||
- **评论语气友好**:重复检测是帮助用户,不是指责
|
||||
- **用户确认优先**:所有写入操作前先预览
|
||||
|
||||
## References
|
||||
|
||||
- [gitlink-faq-collect](references/gitlink-faq-collect.md) — Issue 数据采集
|
||||
- [gitlink-faq-cluster](references/gitlink-faq-cluster.md) — 分类+聚类算法
|
||||
- [gitlink-faq-generate](references/gitlink-faq-generate.md) — 知识库文档生成
|
||||
- [gitlink-faq-detect](references/gitlink-faq-detect.md) — 重复检测逻辑
|
||||
- [gitlink-faq-publish](references/gitlink-faq-publish.md) — Wiki 发布
|
||||
- [weekly-faq-refresh-workflow](examples/weekly-faq-refresh-workflow.md) — 定期刷新示例
|
||||
- [duplicate-detection-demo](examples/duplicate-detection-demo.md) — 重复检测示例
|
||||
- [faq-template](examples/faq-template.md) — 文档模板
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
# 重复检测完整示例
|
||||
|
||||
> 演示对新 Issue 进行查重检测的端到端流程。
|
||||
|
||||
## 场景
|
||||
|
||||
有用户提交了一个新 Issue #312,标题是"登录的时候一直转圈"。维护者想确认这是不是之前已经被问过的问题。
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: 用户发起查重请求
|
||||
|
||||
```
|
||||
用户: 帮我看看 Issue #312 是不是重复了
|
||||
|
||||
AI: 好的,先看一下这个 Issue 的详情。
|
||||
```
|
||||
|
||||
### Step 2: 获取目标 Issue
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --number 312 --format json
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"subject": "登录的时候一直转圈",
|
||||
"description": "打开登录页面,输入账号密码点击登录,页面一直转圈不跳转,换了浏览器也不行"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: 获取对比数据
|
||||
|
||||
```bash
|
||||
# 获取 FAQ
|
||||
gitlink-cli wiki +view --title "FAQ" --format json
|
||||
|
||||
# 获取已关闭 Issue
|
||||
gitlink-cli issue +list --state closed --limit 100 --format json
|
||||
```
|
||||
|
||||
### Step 4: AI 匹配分析
|
||||
|
||||
```
|
||||
AI 对比分析中...
|
||||
```
|
||||
|
||||
返回匹配结果:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 🔍 Issue #312 查重结果 │
|
||||
│ │
|
||||
│ 标题: "登录的时候一直转圈" │
|
||||
│ │
|
||||
│ 高度重复 (≥0.85): │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ 📖 FAQ Q3: 登录失败或提示 401 错误 │ │
|
||||
│ │ 相似度: 0.91 │ │
|
||||
│ │ 答案: Token 过期或网络问题,尝试重新登录 │ │
|
||||
│ ├──────────────────────────────────────────────┤ │
|
||||
│ │ 🔗 Issue #142: 登录页面显示空白 │ │
|
||||
│ │ 相似度: 0.88 │ │
|
||||
│ │ 已解决: 浏览器缓存问题 │ │
|
||||
│ ├──────────────────────────────────────────────┤ │
|
||||
│ │ 🔗 Issue #205: 无法登录,页面无响应 │ │
|
||||
│ │ 相似度: 0.86 │ │
|
||||
│ │ 已解决: DNS 解析问题 │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 判定: 高度重复 (top_match: 0.91) │
|
||||
│ 建议: 添加引导评论 + duplicate 标签 │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Step 5: 用户确认
|
||||
|
||||
```
|
||||
AI: 检测到 3 个高度相似的匹配。建议:
|
||||
A. 添加引导评论(推荐用户查看 FAQ Q3 和 #142)
|
||||
B. 添加 duplicate 标签后关闭
|
||||
C. 仅查看,不操作
|
||||
|
||||
用户: 选 A
|
||||
```
|
||||
|
||||
### Step 6: 执行操作
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +comment --number 312 --body "你好!检测到你的问题与已有内容高度相似:
|
||||
|
||||
- 📖 [FAQ - Q3: 登录失败或提示 401 错误](wiki/FAQ)
|
||||
- 🔗 Issue #142: 登录页面显示空白(浏览器缓存问题)
|
||||
- 🔗 Issue #205: 无法登录,页面无响应(DNS 解析问题)
|
||||
|
||||
建议先查看以上内容。常见原因包括:浏览器缓存、DNS 解析、Token 过期。
|
||||
如果以上方案无法解决,请补充错误截图或浏览器控制台日志,我们会进一步排查。"
|
||||
```
|
||||
|
||||
### Step 7: 完成
|
||||
|
||||
```
|
||||
AI: 已为 Issue #312 添加引导评论。用户将收到通知,看到参考链接。
|
||||
```
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# 📊 Issue 知识库
|
||||
|
||||
> 自动生成 | 数据来源:已关闭 Issue({N} 条有效)
|
||||
> 更新时间:{DATE}
|
||||
> 项目:{OWNER}/{REPO}
|
||||
|
||||
---
|
||||
|
||||
## 📂 概览
|
||||
|
||||
| 类型 | 数量 | 聚类数 |
|
||||
|------|------|--------|
|
||||
| 🐛 Bug 报告 | {BUG_COUNT} | {BUG_CLUSTER_COUNT} 个模块 |
|
||||
| 💡 功能请求 | {FEATURE_COUNT} | {FEATURE_CLUSTER_COUNT} 个领域 |
|
||||
| 📖 使用问题 | {QUESTION_COUNT} | {QUESTION_CLUSTER_COUNT} 个主题 |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bug 高频模块
|
||||
|
||||
### {模块名}({N} 个 Bug)🔥🔥
|
||||
|
||||
**典型症状**: {一句话描述这类 Bug 的共同表现}
|
||||
|
||||
**涉及 Issue**: [#{编号}]({链接}), [#{编号}]({链接})
|
||||
|
||||
**已知修复**: {如有统一修复方案则写,无则写"部分已单独修复,详见表中 Issue"}
|
||||
|
||||
---
|
||||
|
||||
### {模块名}({N} 个 Bug)🔥
|
||||
|
||||
**典型症状**: ...
|
||||
|
||||
**涉及 Issue**: ...
|
||||
|
||||
**已知修复**: ...
|
||||
|
||||
---
|
||||
|
||||
> 如无高频 Bug(均 < 2 条),本章标注"暂无高频 Bug 模式,Bug 报告较分散"。
|
||||
|
||||
---
|
||||
|
||||
## 💡 功能请求热度
|
||||
|
||||
### {功能领域}({N} 个请求)🔥🔥🔥
|
||||
|
||||
**用户期望**: {一句话总结用户想要什么}
|
||||
|
||||
**涉及 Issue**: [#{编号}]({链接}), [#{编号}]({链接})
|
||||
|
||||
---
|
||||
|
||||
### {功能领域}({N} 个请求)🔥
|
||||
|
||||
**用户期望**: ...
|
||||
|
||||
**涉及 Issue**: ...
|
||||
|
||||
---
|
||||
|
||||
> 如无热门功能请求,本章标注"暂无集中的功能请求"。
|
||||
|
||||
---
|
||||
|
||||
## 📖 常见使用问题
|
||||
|
||||
### Q{N}: {问题}?
|
||||
|
||||
**A:** {答案}
|
||||
|
||||
> 📎 来源 Issue: [#{编号}]({链接}), [#{编号}]({链接})
|
||||
|
||||
### Q{N}: {问题}?
|
||||
|
||||
**A:** {答案}
|
||||
|
||||
> 📎 来源 Issue: [#{编号}]({链接})
|
||||
|
||||
---
|
||||
|
||||
> 如无常见使用问题,本章标注"暂无常见使用问题"。
|
||||
|
||||
---
|
||||
|
||||
> 💡 本文档由 gitlink-faq 自动生成,建议每 1-2 周更新。
|
||||
> 🔗 提交新 Issue?请先确认是否已被收录。
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# 每周刷新 Issue 知识库示例
|
||||
|
||||
> 端到端演示:定期归纳已关闭 Issue,更新知识库 Wiki。
|
||||
|
||||
## 场景
|
||||
|
||||
gitlink-cli 项目维护者,本周关闭了 20 个 Issue,想更新知识库。
|
||||
|
||||
## 完整对话流程
|
||||
|
||||
### 触发
|
||||
|
||||
```
|
||||
用户: 帮我整理这周关闭的 Issue,更新知识库
|
||||
|
||||
AI: 好的,先采集最近关闭的 Issue。
|
||||
```
|
||||
|
||||
### Step 1-2: 采集 + 筛选
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list --state closed --limit 100 --format json
|
||||
```
|
||||
|
||||
```
|
||||
AI 采集到 20 个已关闭 Issue:
|
||||
- 排除 5 个测试 Issue
|
||||
- 剩余 15 个有效 Issue,读取详情中...
|
||||
```
|
||||
|
||||
### Step 3-4: 分类 + 聚类
|
||||
|
||||
```
|
||||
AI 展示分析结果:
|
||||
|
||||
📊 Issue 分类结果(共 15 条有效)
|
||||
|
||||
类型分布:
|
||||
| 类型 | 数量 | 占比 |
|
||||
|------|------|------|
|
||||
| 🐛 Bug 报告 | 8 | 53% |
|
||||
| 💡 功能请求 | 5 | 33% |
|
||||
| 📖 使用问题 | 2 | 13% |
|
||||
|
||||
───────────────
|
||||
|
||||
🐛 Bug 高频模块:
|
||||
| 模块 | Bug 数 | 典型症状 |
|
||||
|------|--------|----------|
|
||||
| Issue/PR 数据展示 | 4 | view 命令返回字段与网页不一致 |
|
||||
| 命令执行 | 2 | 特定参数下命令静默失败 |
|
||||
| 平台兼容 | 2 | Windows 下行为异常 |
|
||||
|
||||
💡 功能请求热度:
|
||||
| 领域 | 请求数 | 用户期望 |
|
||||
|------|--------|----------|
|
||||
| API 查询增强 | 3 | 支持更丰富的查询和字段返回 |
|
||||
|
||||
📖 使用问题(2 条):
|
||||
- 安装后如何配置环境变量?
|
||||
- API 命令占位符怎么用?
|
||||
```
|
||||
|
||||
### Step 5: 生成并确认
|
||||
|
||||
```
|
||||
AI 生成知识库 Markdown,展示前几段预览...
|
||||
|
||||
用户: 可以,发布到 Wiki
|
||||
|
||||
AI: 好的,更新 Wiki 页面...
|
||||
```
|
||||
|
||||
### Step 6: 发布
|
||||
|
||||
```bash
|
||||
gitlink-cli wiki +update --title "Issue-知识库" --file ./issue-knowledge-base.md
|
||||
```
|
||||
|
||||
```
|
||||
AI: ✅ 知识库已更新!
|
||||
- Wiki 页面: Issue-知识库
|
||||
- 本次新增: 15 条 Issue → 6 个聚类
|
||||
- 下次建议: 2 周后刷新
|
||||
```
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
# gitlink-faq 分类与聚类
|
||||
|
||||
> 本文档说明 AI 如何对 Issue 先分类(Bug/功能请求/使用问题),再在同一类型内聚类。
|
||||
|
||||
## 分析前提
|
||||
|
||||
**聚类仅基于 `subject` + `description` 两个字段**。GitLink API 不支持读取 Issue 评论(journals),无法获取讨论/解决方案。但优秀的 description 往往包含:复现步骤、环境信息、修复建议、详细场景描述——这些都是高质量聚类的基础。
|
||||
|
||||
## 两步流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Step 1: 类型分类 │
|
||||
│ 每条 Issue → AI 判断类型 │
|
||||
│ Bug / Feature Request / Usage Question │
|
||||
│ 不确定的归入"其他"但保留,不丢弃 │
|
||||
└──────────────────┬──────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Step 2: 类型内聚类 │
|
||||
│ Bug → 按出问题的模块分类 │
|
||||
│ Feature → 按功能领域分类 │
|
||||
│ Usage → 按操作场景分类 │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Step 1: 类型分类
|
||||
|
||||
### 分类 Prompt
|
||||
|
||||
```
|
||||
你正在分析一个项目的已关闭 Issue。请对每条 Issue 判断它属于以下哪种类型:
|
||||
|
||||
类型定义:
|
||||
- bug: 描述异常行为、报错、行为与预期不符、数据缺失/错误
|
||||
- feature: 建议新增功能、增强现有能力、改进体验
|
||||
- question: 不知道如何使用、配置不清楚、询问是否支持某能力
|
||||
- other: 不属于以上三类(如测试、讨论、公告)
|
||||
|
||||
返回 JSON 数组:
|
||||
[{
|
||||
"issue_number": N,
|
||||
"subject": "标题",
|
||||
"type": "bug|feature|question|other",
|
||||
"reason": "一句话判断依据"
|
||||
}]
|
||||
```
|
||||
|
||||
### 分类规则
|
||||
|
||||
| 类型 | 判断信号 | 反例(容易误判) |
|
||||
|------|----------|-----------------|
|
||||
| **bug** | 含"报错""不生效""异常""不一致""缺失""无法"等 | "希望能 xx"是 feature 不是 bug |
|
||||
| **feature** | 含"希望""建议""能否支持""加一个""要是能"等 | "xx 不支持"可能是 question |
|
||||
| **question** | 含"怎么""如何""能不能""是否支持"等,且是咨询性质 | "xx 报错怎么办"→ 先确认是不是 bug |
|
||||
| **other** | 标题含"test""测试""讨论""收集"等 | 不确定时归为 other |
|
||||
|
||||
## Step 2: 类型内聚类
|
||||
|
||||
### Bug 聚类
|
||||
|
||||
按**出问题的模块/组件/功能**归类,找出高频 Bug 模式。
|
||||
|
||||
聚类维度:
|
||||
- 影响的是哪个命令/接口(如 `issue +view`、`pr +list`、`api` 命令)
|
||||
- 问题类型是否相同(数据显示错误 ×4 / 命令执行失败 ×2 / 平台兼容 ×1)
|
||||
- Bug 之间是否存在共同根因
|
||||
|
||||
输出格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "Issue/PR 数据展示",
|
||||
"bug_count": 4,
|
||||
"pattern": "CLI 返回数据字段与网页端不一致或字段缺失",
|
||||
"affected_issues": [5, 7, 15, 18],
|
||||
"typical_symptom": "使用 view 命令查看详情时,部分字段(如关闭时间、描述等)缺失或与网页端不一致"
|
||||
}
|
||||
```
|
||||
|
||||
### 功能请求聚类
|
||||
|
||||
按**请求的功能领域**归类,找出用户需求热度。
|
||||
|
||||
聚类维度:
|
||||
- 请求的是哪个能力方向(API 增强 / 命令扩展 / 平台支持)
|
||||
- 是否指向同一个需求的不同表述
|
||||
- 是否有用户点赞/讨论热度叠加
|
||||
|
||||
输出格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"feature_area": "API 查询能力增强",
|
||||
"request_count": 3,
|
||||
"pattern": "用户希望 API 支持更丰富的查询和操作",
|
||||
"affected_issues": [9, 14, 21],
|
||||
"common_ask": "支持按序号查询、返回完整时间字段、读取仓库文件"
|
||||
}
|
||||
```
|
||||
|
||||
### 使用问题聚类(传统 Q&A)
|
||||
|
||||
与传统 FAQ 一致:按操作场景归类,提炼"问题 → 答案"。
|
||||
|
||||
仅当同类 ≥ 2 条时生成 Q&A 条目。
|
||||
|
||||
## 批次合并
|
||||
|
||||
多批次处理后的合并规则:
|
||||
|
||||
1. 同类型的相同模块/领域 → 合并,更新 issue_count
|
||||
2. 跨类型的关联 Issue 标注引用(如一个 Bug 可能由某个 Feature Request 修复)
|
||||
3. 最终按 `bug_count` / `request_count` 降序排列
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
# gitlink-faq 数据采集
|
||||
|
||||
> 本文档详细说明 FAQ 生成时 Issue 数据的采集策略和参数。
|
||||
|
||||
## 数据源
|
||||
|
||||
| 数据 | 命令 | 说明 |
|
||||
|------|------|------|
|
||||
| Issue 列表(open) | `issue +list --state open --limit 100 --format json` | 仍开放的 Issue |
|
||||
| Issue 列表(closed) | `issue +list --state closed --limit 100 --format json` | 已关闭的 Issue |
|
||||
| Issue 详情 | `issue +view --number N --format json` | 含标题、描述、journals(评论历史) |
|
||||
|
||||
> **关键认知**:GitLink 平台很多已解决的 Issue 不会被及时设为"关闭"状态,存在延迟甚至从未更改状态。因此必须**同时采集 open 和 closed 两份列表**,合并去重后才能得到完整的可分析 Issue 集合。用 `issue +view` 读取的 `journals`(评论讨论历史)比 `status` 字段更能判断一个 Issue 是否"已解决"。
|
||||
|
||||
## 筛选策略
|
||||
|
||||
### 有价值 vs 无价值 Issue
|
||||
|
||||
采集后需筛选。注意:GitLink API **不支持读取 Issue 评论(journals)**,筛选只能基于 subject + description。
|
||||
|
||||
**宽松筛选原则**(只排除明确无价值的):
|
||||
|
||||
| 类型 | 是否纳入 | 原因 |
|
||||
|------|----------|------|
|
||||
| 有 description 的 Issue | ✅ 纳入 | description 可能非常详细 |
|
||||
| 仅有标题无 description | ✅ 纳入 | 标题本身有价值信息 |
|
||||
| 功能请求 | ✅ 纳入 | 反映用户需求优先级 |
|
||||
| 标题含 `[test]`/`测试` | ❌ 排除 | 纯测试数据 |
|
||||
| 标题为空或纯占位符 | ❌ 排除 | 无有效信息 |
|
||||
|
||||
> 不再排除"功能请求"和"仅有标题"的 Issue。没有评论数据的情况下,最大化保留所有可分析的 Issue。
|
||||
|
||||
### 分批采集
|
||||
|
||||
```
|
||||
Issue 总量 采集策略
|
||||
───────── ─────────
|
||||
< 20 全部采集,逐个读详情(含 journals)
|
||||
20-50 全部采集,按标题粗筛后读重点 Issue 详情
|
||||
50-100 分批采集(每批50),先按标题粗筛
|
||||
> 100 取最近活跃的 100 个,优先高参与度的
|
||||
```
|
||||
|
||||
### 参与度筛选
|
||||
|
||||
优先采集"高参与度"的 Issue(更有分析价值):
|
||||
- **journals 非空**(有人讨论过)——这是最重要的筛选信号,空 journals 的 Issue 分析价值极低
|
||||
- journals 中包含维护者回复(优先提取作为答案/修复方案)
|
||||
- 评论数量 ≥ 2
|
||||
|
||||
### API 限制:无法读取评论
|
||||
|
||||
`issue +view` 只返回 `comment_journals_count`(评论数),不返回评论内容。`GET /v1/.../issues/{N}/journals` 端点返回 HTML 而非 JSON,无法通过 API 获取评论正文。
|
||||
|
||||
因此分析只能基于 `subject` + `description` 两个字段。这也是筛选规则放宽的原因——没有评论作为补充信息,凭标题和描述能分析到的内容更有限,需要尽可能保留更多 Issue 来保证覆盖度。
|
||||
|
||||
## 输出数据格式
|
||||
|
||||
采集后整理为以下结构供聚类使用:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"number": 142,
|
||||
"subject": "安装后运行报 command not found",
|
||||
"description": "按照 README 安装后,终端输入 gitlink-cli 提示...",
|
||||
"labels": ["bug", "installation"],
|
||||
"journal_count": 5,
|
||||
"last_comment_author": "maintainer",
|
||||
"resolution": "需要将 ~/.local/bin 加入 PATH"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## API 注意事项
|
||||
|
||||
- `issue +list` 的 `--limit` 最大 200,超出需分页(`--page` 参数)
|
||||
- **`--state` 参数不可靠**:与 PR 列表类似,`--state` 参数可能不严格过滤列表(返回数据中 `closed_count` 才是真实统计)。必须同时拉取 open 和 closed 两份列表并合并去重。
|
||||
- `issue +view` 的 `journals` 字段包含完整评论历史,是判断解决过程的关键
|
||||
- `journals` 中的 `body` 字段是评论的纯文本内容,`author.login` 是评论者
|
||||
- 大量请求时建议用 `--debug` 查看实际请求 URL,确认分页参数正确
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
# gitlink-faq 重复检测
|
||||
|
||||
> 本文档说明如何处理新 Issue 的重复检测——对比已有 FAQ 和历史 Issue。
|
||||
|
||||
## 检测流程
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Step 1: 确定搜索目标 │
|
||||
│ 编号 → issue +view 获取目标 │
|
||||
│ 关键词 → 进入全量搜索模式 │
|
||||
└────────────────┬───────────────────────────┘
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Step 2: 拉取全量列表 │
|
||||
│ issue +list --state open │
|
||||
│ issue +list --state closed │
|
||||
│ 标题匹配筛选候选 Issue │
|
||||
└────────────────┬───────────────────────────┘
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Step 3: 自动读详情(不需等用户确认) │
|
||||
│ 对每条候选 → issue +view --number N │
|
||||
│ 提取: subject + description + journals │
|
||||
└────────────────┬───────────────────────────┘
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Step 4: 分析 & 输出结论 │
|
||||
│ 综合标题+描述+journals 给出: │
|
||||
│ - 核心讨论内容摘要 │
|
||||
│ - 维护者是否有回复/方案 │
|
||||
│ - 相关度判定 │
|
||||
└────────────────┬───────────────────────────┘
|
||||
▼
|
||||
┌────────────────────────────────────────────┐
|
||||
│ Step 5: 用户确认后执行写操作(可选) │
|
||||
│ > 0.85 → comment + label │
|
||||
│ 0.6-0.85 → comment only │
|
||||
│ < 0.6 → 无需操作 │
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 相似度匹配 Prompt
|
||||
|
||||
```
|
||||
你正在判断一个新提交的 Issue 是否与已有问题重复。
|
||||
|
||||
## 新 Issue
|
||||
标题: {subject}
|
||||
描述: {description}
|
||||
|
||||
## 候选匹配列表
|
||||
{候选列表,每条含: 来源、标题、摘要}
|
||||
|
||||
请对每个候选给出 0-1 的相似度评分:
|
||||
- > 0.85: 问的是同一个问题(只是表述不同)
|
||||
- 0.6-0.85: 有关联但不是同一个问题(如"登录报 401"
|
||||
和"Token 配置")
|
||||
- < 0.6: 无关
|
||||
|
||||
返回 JSON 数组,按相似度降序排列。
|
||||
```
|
||||
|
||||
## 输出 JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"target_issue": {
|
||||
"number": 245,
|
||||
"title": "登录页面打不开",
|
||||
"description": "点击登录按钮后页面白屏..."
|
||||
},
|
||||
"matches": [
|
||||
{
|
||||
"source": "FAQ",
|
||||
"entry": "Q3: 登录失败或提示 401 错误怎么处理?",
|
||||
"similarity": 0.92,
|
||||
"level": "high_duplicate"
|
||||
},
|
||||
{
|
||||
"source": "issue",
|
||||
"number": 142,
|
||||
"title": "登录页面显示空白",
|
||||
"summary": "用户反馈登录页白屏,最终确认是浏览器缓存问题",
|
||||
"similarity": 0.88,
|
||||
"level": "high_duplicate"
|
||||
},
|
||||
{
|
||||
"source": "issue",
|
||||
"number": 178,
|
||||
"title": "Token 刷新机制咨询",
|
||||
"summary": "询问 Token 有效期和刷新策略",
|
||||
"similarity": 0.65,
|
||||
"level": "related"
|
||||
}
|
||||
],
|
||||
"recommendation": "high_duplicate",
|
||||
"top_match_similarity": 0.92
|
||||
}
|
||||
```
|
||||
|
||||
## 评论模板
|
||||
|
||||
### 高度重复(similarity > 0.85)
|
||||
|
||||
```
|
||||
你好!检测到你的问题与已有内容高度相似:
|
||||
|
||||
- 📖 [FAQ - {条目名}]({FAQ 链接})
|
||||
- 🔗 相关 Issue: #{编号} - {标题}
|
||||
|
||||
建议先查看以上内容。如果无法解决你的问题,请补充更多细节(如错误日志、操作步骤),我们会进一步排查。
|
||||
```
|
||||
|
||||
### 可能相关(0.6 ~ 0.85)
|
||||
|
||||
```
|
||||
你好!你的问题可能与以下内容相关,供参考:
|
||||
|
||||
- {匹配条目列表}
|
||||
|
||||
如果这些不解决你的问题,请提供更多上下文。
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **不要误判**:问题表述相似但根因不同(如"打不开"可能是网络问题也可能是代码 bug),相似度 < 0.85 时只建议参考不打标签
|
||||
- **同一用户的多条 Issue**:如果同一用户就同一问题连续发 Issue,先合并讨论再判断
|
||||
- **对事不对人**:评论始终友好,引导用户找到答案而非指责
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# gitlink-faq 文档生成
|
||||
|
||||
> 如何将分类+聚类结果组织为结构化知识库 Markdown。
|
||||
|
||||
## 生成原则
|
||||
|
||||
- **按类型分章节**:Bug → 功能请求 → 使用问题,每章独立
|
||||
- **按热度排序**:同类内 Issue 数量多的排在前面
|
||||
- **数据不足时省略**:某类型 < 2 条时标注"暂无足够数据",不强行展开
|
||||
- **来源可追溯**:每条结论后附来源 Issue 编号
|
||||
|
||||
## 文档结构
|
||||
|
||||
参考 [`examples/faq-template.md`](../examples/faq-template.md)。
|
||||
|
||||
```markdown
|
||||
# 📊 Issue 知识库
|
||||
|
||||
> 自动生成 | 数据来源:已关闭 Issue({N} 条有效)
|
||||
> 更新时间:{DATE}
|
||||
> 项目:{OWNER}/{REPO}
|
||||
|
||||
## 📂 概览
|
||||
|
||||
| 类型 | 数量 | 聚类数 |
|
||||
|------|------|--------|
|
||||
| 🐛 Bug 报告 | {N} | {M} 个模块 |
|
||||
| 💡 功能请求 | {N} | {M} 个领域 |
|
||||
| 📖 使用问题 | {N} | {M} 个主题 |
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Bug 高频模块
|
||||
|
||||
### {模块名}({N} 个 Bug)
|
||||
|
||||
**典型症状**: {描述}
|
||||
**涉及 Issue**: [#{编号}]({链接}), [#{编号}]({链接})
|
||||
**已知修复**: {如有则写,无则写"暂未统一修复"}
|
||||
|
||||
### {模块名}({N} 个 Bug)
|
||||
|
||||
...
|
||||
|
||||
> 如该类 < 2 条,标注"暂无高频 Bug 模式"。
|
||||
|
||||
---
|
||||
|
||||
## 💡 功能请求热度
|
||||
|
||||
### {功能领域}({N} 个请求)🔥
|
||||
|
||||
**用户期望**: {一句话总结}
|
||||
**涉及 Issue**: [#{编号}]({链接}), [#{编号}]({链接})
|
||||
|
||||
### {功能领域}({N} 个请求)
|
||||
|
||||
...
|
||||
|
||||
> 如该类 < 2 条,标注"暂无热门功能请求"。
|
||||
|
||||
---
|
||||
|
||||
## 📖 常见使用问题
|
||||
|
||||
### Q{N}: {问题}?
|
||||
|
||||
**A:** {答案}
|
||||
|
||||
> 📎 来源 Issue: [#{编号}]({链接}), [#{编号}]({链接})
|
||||
|
||||
...
|
||||
|
||||
> 如该类 < 2 条,标注"暂无常见使用问题"。
|
||||
|
||||
---
|
||||
|
||||
> 💡 本文档由 gitlink-faq 自动生成,建议每 1-2 周更新一次。
|
||||
```
|
||||
|
||||
## 热度标注
|
||||
|
||||
| Issue 数 | 热度 |
|
||||
|----------|------|
|
||||
| ≥ 5 | 🔥🔥🔥 高频 |
|
||||
| 3-4 | 🔥🔥 常见 |
|
||||
| 2 | 🔥 偶发 |
|
||||
| 1 | 不纳入(标注为单次事件) |
|
||||
|
||||
## 答案/解决状态标注
|
||||
|
||||
对于 Bug 和功能请求,标注其当前状态:
|
||||
|
||||
| 状态 | 标注 | 条件 |
|
||||
|------|------|------|
|
||||
| ✅ 已修复/已实现 | 绿色标记 | Issue 关闭且 journals 中有修复记录 |
|
||||
| 🔧 部分修复 | 黄色标记 | 有修复但不完整 |
|
||||
| ❓ 状态不明 | 无标记 | journals 为空或无明确解决记录 |
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# gitlink-faq Wiki 发布
|
||||
|
||||
> 本文档说明如何将生成的 FAQ 内容发布到 GitLink 项目 Wiki。
|
||||
|
||||
## 发布命令
|
||||
|
||||
### 首次创建知识库页面
|
||||
|
||||
```bash
|
||||
gitlink-cli wiki +create \
|
||||
--title "Issue-知识库" \
|
||||
--file ./issue-knowledge-base.md \
|
||||
--message "自动生成:从已关闭 Issue 归纳分类"
|
||||
```
|
||||
|
||||
### 更新已有知识库页面
|
||||
|
||||
```bash
|
||||
# 预览变更
|
||||
gitlink-cli wiki +update --title "Issue-知识库" --file ./issue-knowledge-base.md --dry-run
|
||||
|
||||
# 覆盖更新
|
||||
gitlink-cli wiki +update --title "Issue-知识库" --file ./issue-knowledge-base.md
|
||||
```
|
||||
|
||||
### 检查知识库是否存在
|
||||
|
||||
```bash
|
||||
# 列出所有 Wiki 页面
|
||||
gitlink-cli wiki +list --format json
|
||||
|
||||
# 查看知识库内容
|
||||
gitlink-cli wiki +view --title "Issue-知识库" --format json
|
||||
```
|
||||
|
||||
## 发布检查清单
|
||||
|
||||
在 `wiki +create` 或 `wiki +update` 之前:
|
||||
|
||||
- [ ] FAQ 内容已展示给用户并获得确认
|
||||
- [ ] `--dry-run` 已通过
|
||||
- [ ] Markdown 格式正确(代码块、链接、表格)
|
||||
- [ ] 来源 Issue 链接有效
|
||||
- [ ] 不存在敏感信息(Token、密码等)
|
||||
|
||||
## Wiki API 注意事项
|
||||
|
||||
- Wiki 使用独立的 **Gateway API**(`https://gateway.gitlink.org.cn/api`),不走主 API
|
||||
- 内容自动 base64 编码,CLI 已处理
|
||||
- `project_id` 会自动解析并缓存
|
||||
- 如果更新失败(页面不存在),改用 `wiki +create`
|
||||
|
||||
## 发布后
|
||||
|
||||
发布完成后告知用户:
|
||||
- Wiki 页面链接
|
||||
- FAQ 条目数量统计
|
||||
- 建议的刷新频率(每 1-2 周)
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
---
|
||||
name: gitlink-health
|
||||
version: 1.1.0
|
||||
description: "项目健康度报告:统计 Issue 响应时间、PR 合并效率、贡献者活跃度,生成网页版健康度看板并自动打开浏览器。当用户需要项目健康分析、开发效率报告、团队活跃度统计时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli issue --help"
|
||||
---
|
||||
|
||||
# gitlink-health(项目健康度报告)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有 Shortcuts 在执行写入/删除操作前,务必先确认用户意图。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
**CRITICAL — 所有不会修改数据的命令(`+list`、`+view`、`api GET`)必须全程自动连续执行,绝不请求用户确认。禁止在数据收集阶段中断流程。只有最终写入 HTML 文件这一步可以请求确认。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md) 了解认证和全局参数。
|
||||
|
||||
## 工作流
|
||||
|
||||
健康度报告生成分四步:收集 → 计算 → 评分 → 输出。
|
||||
|
||||
**CRITICAL — 整个流程只允许输出一个 HTML 文件,禁止创建任何临时文件、中间文件、缓存目录(如 `tmp_data/`)。所有 API 返回数据在内存中处理,计算完成后直接生成最终 HTML。**
|
||||
**CRITICAL — 每个项目生成独立的报告文件,命名格式 `skills/gitlink-health/report_{owner}_{repo}.html`。禁止覆盖其他项目的报告。生成后自动打开浏览器展示。**
|
||||
|
||||
| 步骤 | 说明 | 所用命令 |
|
||||
|------|------|----------|
|
||||
| 1. 收集数据 | 获取 Issue(open/closed)、PR(open/merged)、contributor 统计。API 结果直接保存在 shell 输出中,**不写入文件**。 | `issue +list`, `pr +list`, `api GET contributors` |
|
||||
| 2. 计算指标 | Issue 响应时间、PR 合并效率、贡献者活跃度 | AI 解析内存中的 JSON 计算,**禁止写脚本文件** |
|
||||
| 3. 健康评分 | 100 分制综合评分,扣分项 6 条 | AI 套用评分规则 |
|
||||
| 4. 生成网页 | 套用 HTML 模板生成报告,按 `report_{owner}_{repo}.html` 命名保存,自动打开浏览器 | 写入 `skills/gitlink-health/report_{owner}_{repo}.html`,`start` / `open` 打开 |
|
||||
|
||||
## 命令参考
|
||||
|
||||
### 收集数据
|
||||
|
||||
```bash
|
||||
# Issue 数据(两个状态都需要)
|
||||
gitlink-cli issue +list --state open --format json
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
|
||||
# PR 数据(两个状态都需要)
|
||||
gitlink-cli pr +list --state open --format json
|
||||
gitlink-cli pr +list --state merged --format json
|
||||
|
||||
# 贡献者统计(Raw API)
|
||||
gitlink-cli api GET /:owner/:repo/contributors --format json
|
||||
|
||||
# 项目活动(Raw API,可选)
|
||||
gitlink-cli api GET /:owner/:repo/activity --format json
|
||||
```
|
||||
|
||||
## 三大指标
|
||||
|
||||
### 1. Issue 响应时间
|
||||
|
||||
支持两种口径,**推荐优先使用响应时间**(新增→已解决):
|
||||
|
||||
| 口径 | 计算方式 | 说明 |
|
||||
|------|----------|------|
|
||||
| **响应时间(推荐)** | `avg(resolved_at - created_at)` | 从创建到解决,反映实际处理效率 |
|
||||
| 全周期时间 | `avg(closed_at - created_at)` | 从创建到正式关闭,反映完整生命周期 |
|
||||
|
||||
| 指标 | 计算方式 | 说明 |
|
||||
|------|----------|------|
|
||||
| 平均响应时间 | `avg(resolved_at - created_at)` 或 `avg(closed_at - created_at)` | 无 `resolved_at` 时用历史 `updated_at` 推断 |
|
||||
| 中位数响应时间 | `median(...)` | 排除极端值影响 |
|
||||
| Issue 积压数 | `count(open Issues)` | 当前待处理的 Issue 数量 |
|
||||
| 按优先级分布 | 按 `priority_id` 分组:低(1)/正常(2)/高(3)/紧急(4) | 高优先级积压更值得关注 |
|
||||
|
||||
> **注意**:GitLink API 不返回 `closed_at`/`resolved_at` 字段。若项目有"先解决后批量关闭"的工作流,`updated_at` 可能被关闭操作覆盖而虚高,应优先从会话历史推断解决时间。
|
||||
|
||||
### 2. PR 合并效率
|
||||
|
||||
PR 合并效率由**三类 PR** 共同决定,需计算**三段时间**:
|
||||
|
||||
| 时间 | 名称 | 公式 | 适用对象 |
|
||||
|------|------|------|----------|
|
||||
| ① | 已合并 PR 平均合并时长 | `avg(merged_at - created_at)` | status=1(已合并) |
|
||||
| ② | 开放 PR 平均等待时长 | `avg(now - created_at)` | status=0(开放中) |
|
||||
| ③ | 加权总平均处理时长 | `(merged/total) * ① + (open/total) * ②` | 已合并 ∪ 开放 |
|
||||
|
||||
**总样本数 = 已合并数 + 开放数**(已关闭未合并的 PR 不参与时间计算,仅参与合并率分母)。
|
||||
|
||||
| 指标 | 计算方式 | 说明 |
|
||||
|------|----------|------|
|
||||
| ① 已合并平均合并时长 | `avg(merged_at - created_at)` | **仅对已合并 PR**(`status=1`)计算,从创建到合并成功的时长 |
|
||||
| ② 开放平均等待时长 | `avg(now - created_at)` | 开放 PR 从创建到当前的等待时长,反映积压压力 |
|
||||
| ③ 加权总平均 | `(merged/total) * ① + (open/total) * ②` | 综合 PR 处理节奏的总体指标 |
|
||||
| PR 积压数 | `count(open PRs)` | 当前待合并的 PR 数量 |
|
||||
| 合并率 | `merged / (merged + closed)` | 已合并占所有已关闭 PR(合并+关闭)的比例 |
|
||||
|
||||
**统计对象规则**:
|
||||
|
||||
| PR 状态 | 计入时间计算 | 计入合并率分母 | 计入积压 |
|
||||
|---------|------------|---------------|---------|
|
||||
| 已合并(status=1) | ① | ✅ | ❌ |
|
||||
| 已关闭未合并(status=2) | ❌ | ✅ | ❌ |
|
||||
| 开放中(status=0) | ② | ❌ | ✅ |
|
||||
|
||||
> **数据获取**:`merged_at` 仅在 `pr +view --id <pull_request_number>` 单个 PR 详情中返回(路径 `data.pull_request.merged_at`),列表接口不暴露。
|
||||
> **绝对禁止**:用「当前时间 - 创建时间」估算已合并 PR 的合并时间。开放 PR 用此公式是允许的(且必要),因为它们没有合并时间点,等待时长反映积压。
|
||||
|
||||
### 3. 贡献者活跃度
|
||||
|
||||
| 指标 | 数据源 | 说明 |
|
||||
|------|--------|------|
|
||||
| 贡献者总数 | `contributors.author_count` | 项目总贡献者数 |
|
||||
| 每人 commits | `contributors.authors[].commits` | 按 commit 数排名 |
|
||||
| 每人增删行数 | `contributors.authors[].additions / deletions` | 代码贡献量 |
|
||||
| 每人 PR 数 / Issue 数 | PR 和 Issue 列表统计 | 在 podium 和 contributor-row 中显示 |
|
||||
| 活跃度分级 | 综合 commits + PRs + Issues | 高频 / 正常 / 低频 |
|
||||
|
||||
**CRITICAL — 每个贡献者必须同时显示 PR 数量和 Issue 数量**,格式为 `N PRs · M Issues`。
|
||||
- **Podium 前三名**(冠亚季军):`N PRs · M Issues · 🔥 高频` — 带活跃度标签。
|
||||
- **其他贡献者**(contributor-row):`N PRs · M Issues` — 不带活跃度标签。
|
||||
|
||||
活跃度分级标准:
|
||||
|
||||
| 级别 | 条件 |
|
||||
|------|------|
|
||||
| 🔥 高频 | 近 30 天 commits ≥ 5 或 PRs ≥ 2 |
|
||||
| 🟢 正常 | 近 30 天 commits ≥ 1 或 PRs ≥ 1 |
|
||||
| 🟡 低频 | 近 30 天无 commit 和 PR,但有近期 Issue 活动 |
|
||||
| ⚪ 不活跃 | 近 60 天无任何活动记录 |
|
||||
|
||||
## 健康度综合评分(100 分制)
|
||||
|
||||
### 核心指标(75 分)
|
||||
|
||||
| 扣分项 | 扣分 | 条件 |
|
||||
|--------|------|------|
|
||||
| Issue 响应慢 | -25 | Issue 平均响应时间(新增→已解决)> 3 天 |
|
||||
| PR 合并慢 | -25 | PR 加权总平均处理时长 > 2 天(③) |
|
||||
| 贡献者活跃度低 | -25 | 近 30 天有活跃行为的贡献者 < 2 人,或单一贡献者占总 commit 数 > 70% |
|
||||
|
||||
### 辅助指标(25 分)
|
||||
|
||||
| 扣分项 | 扣分 | 条件 |
|
||||
|--------|------|------|
|
||||
| Issue 积压严重 | -10 | open 状态 Issue 数量 > 20 |
|
||||
| PR 积压严重 | -10 | open 状态 PR 数量 > 10 |
|
||||
| 近期无发布 | -5 | 最近 30 天无新 Release |
|
||||
|
||||
评分等级:
|
||||
|
||||
| 分数 | 等级 | 图标 |
|
||||
|------|------|------|
|
||||
| 90-100 | 优秀 | 🟢 |
|
||||
| 70-89 | 良好 | 🔵 |
|
||||
| 50-69 | 一般 | 🟡 |
|
||||
| 30-49 | 需关注 | 🟠 |
|
||||
| 0-29 | 严重 | 🔴 |
|
||||
|
||||
## 报告输出(HTML 网页)
|
||||
|
||||
### 模板文件
|
||||
|
||||
报告使用 `skills/gitlink-health/template.html` 作为模板。AI 将计算后的指标填入模板中的 `{{PLACEHOLDER}}` 占位符,按 `report_{owner}_{repo}.html` 格式命名(如 `report_chroe_gitlink-cli.html`),生成到 `skills/gitlink-health/` 目录下。每个项目独立文件,不覆盖其他项目报告。
|
||||
|
||||
### 占位符说明
|
||||
|
||||
| 占位符 | 来源 | 说明 |
|
||||
|--------|------|------|
|
||||
| `{{OWNER}}` / `{{REPO}}` | git remote 解析 | 项目路径 |
|
||||
| `{{DATE}}` | 当前日期 | 报告生成日期 |
|
||||
| `{{PERIOD_DAYS}}` | 默认 30 | 统计周期天数 |
|
||||
| `{{SCORE}}` | 计算得出 | 综合评分 0-100 |
|
||||
| `{{SCORE_COLOR}}` | 评分映射 | #00b894(优秀) / #0984e3(良好) / #fdcb6e(一般) / #e17055(需关注) / #d63031(严重) |
|
||||
| `{{SCORE_DASH}}` | 评分计算 | SVG stroke-dasharray:`(SCORE/100*377) 377` |
|
||||
| `{{GRADE}}` | 评分映射 | 优秀 / 良好 / 一般 / 需关注 / 严重 |
|
||||
| `{{DEDUCTION_ROWS}}` | 扣分明细 | 6 行 `<tr>`,每行含指标名、实际值、扣分、状态 |
|
||||
| `{{TOTAL_ISSUES}}` 等 | 统计数据 | Issue/PR 各项数值 |
|
||||
| `{{PRIORITY_BAR}}` | 优先级分布 | 4 个 `<span>` 表示紧急/高/正常/低占比 |
|
||||
| `{{PRIORITY_LEGEND}}` | 优先级分布 | 图例说明 |
|
||||
| `{{CONTRIBUTOR_ROWS}}` | 贡献者统计 | 每人一行的表格数据 |
|
||||
| `{{SUGGESTIONS}}` | AI 生成 | 改进建议列表 |
|
||||
|
||||
### 生成并打开
|
||||
|
||||
1. 将计算后的数据填入模板所有占位符
|
||||
2. 写入 `skills/gitlink-health/report_{owner}_{repo}.html`(每个项目独立文件,不覆盖)
|
||||
3. 写入成功后立即根据操作系统自动打开浏览器:
|
||||
- Windows: `start skills/gitlink-health/report_{owner}_{repo}.html`
|
||||
- macOS: `open skills/gitlink-health/report_{owner}_{repo}.html`
|
||||
- Linux: `xdg-open skills/gitlink-health/report_{owner}_{repo}.html`
|
||||
|
||||
## API 注意事项
|
||||
|
||||
- `GET /:owner/:repo/contributors` 无 Shortcut,通过 `gitlink-cli api GET` 调用
|
||||
- PR list 的 `--state` 仅影响统计计数,返回列表需客户端按 `pull_request_status` 过滤
|
||||
- Issue 字段名:网页编号为 `project_issues_index`,数据库 ID 为 `id`
|
||||
- Issue 时间字段:API **不返回**独立的 `closed_at` 或 `resolved_at`,仅有 `created_at` 和 `updated_at`。`updated_at` 是最后更新时间(可能反映解决时间或关闭时间,需根据上下文判断)
|
||||
- 贡献者统计基于默认分支,不含其他分支的 commit
|
||||
- `contributors` 返回 `author_count`(总数)和 `authors[]`(每人明细)
|
||||
|
||||
## References
|
||||
|
||||
- [collect-data](references/collect-data.md) — 数据收集详细说明
|
||||
- [health-metrics](references/health-metrics.md) — 指标计算和评分规则
|
||||
- [generate-report](references/generate-report.md) — 报告生成和输出
|
||||
- [full-workflow](examples/full-workflow.md) — 完整端到端示例
|
||||
- [gitlink-shared](../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
# 项目健康度报告 — 完整生成示例
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
> **适用场景:** AI Agent 端到端生成项目健康度报告,以 `zzx-coder/gitlink-cli` 为例。
|
||||
|
||||
## 完整流程
|
||||
|
||||
### 第一步:收集 Issue 数据
|
||||
|
||||
```bash
|
||||
# 获取未关闭 Issue
|
||||
gitlink-cli issue +list --state open --format json
|
||||
|
||||
# 获取已关闭 Issue
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
|
||||
# AI 从返回中提取:
|
||||
# - open count: 28
|
||||
# - closed count: 1 (我们之前创建的 #29,已关闭)
|
||||
# - priority 分布: 大部分为 normal (priority_id=2)
|
||||
# - 平均关闭时间: #29 创建后约 1 分钟关闭(测试 issue)
|
||||
```
|
||||
|
||||
### 第二步:收集 PR 数据
|
||||
|
||||
```bash
|
||||
# 获取未合并 PR
|
||||
gitlink-cli pr +list --state open --format json
|
||||
|
||||
# 获取已合并 PR
|
||||
gitlink-cli pr +list --state merged --format json
|
||||
|
||||
# AI 从返回中提取:
|
||||
# - merged count: 10
|
||||
# - open count: 0
|
||||
# - 合并率: 10/11 = 91%
|
||||
# - 最新合并 PR: #11 (2026-06-04)
|
||||
# - PR 平均合并时间需逐条计算
|
||||
```
|
||||
|
||||
### 第三步:收集贡献者统计
|
||||
|
||||
```bash
|
||||
# Raw API 获取贡献者数据
|
||||
gitlink-cli api GET /:owner/:repo/contributors --format json
|
||||
|
||||
# AI 从返回中提取:
|
||||
# - author_count: 4+
|
||||
# - 各贡献者 commit 数和增删行数
|
||||
# - 集中度计算
|
||||
```
|
||||
|
||||
### 第四步:AI 计算指标
|
||||
|
||||
AI 根据 [指标计算规则](../references/health-metrics.md) 处理数据:
|
||||
|
||||
```
|
||||
Issue 响应时间:
|
||||
平均 0.1 天(仅 1 个已关闭 Issue,样本量不足)
|
||||
积压 28 个 → 触发扣分
|
||||
|
||||
PR 合并效率:
|
||||
平均合并时间 ≈ 3.5 天(估算,需 merge_at 字段)
|
||||
积压 0 个 ✓
|
||||
合并率 91%
|
||||
|
||||
贡献者活跃度:
|
||||
mengcheng (camelliamc) — 🔥 高频
|
||||
zzx-coder — 🔥 高频
|
||||
wbtiger — 🟢 正常
|
||||
wangyue789 — 🟡 低频
|
||||
|
||||
综合评分:
|
||||
起始 100
|
||||
核心指标: 0 扣分(响应时间样本不足/PR合并正常/贡献者活跃)
|
||||
辅助指标: -10 Issue 积压 (28 open), -5 无近期发布 (已解决)
|
||||
最终: 85/100(良好 🔵)
|
||||
```
|
||||
|
||||
### 第五步:填充 HTML 模板
|
||||
|
||||
AI 读取 `skills/gitlink-health/template.html`,将计算后的指标替换所有 `{{PLACEHOLDER}}` 占位符,生成完整的 HTML 文件。
|
||||
|
||||
### 第六步:输出报告
|
||||
|
||||
1. 写入 `skills/gitlink-health/report.html`(唯一的输出文件)
|
||||
2. 自动打开浏览器展示报告
|
||||
3. 如需持久化,可选发布为 Issue(内容从内存生成,不写本地文件)
|
||||
|
||||
## AI Agent 执行要点
|
||||
|
||||
1. **数据收集顺序**:先收集 Issue 和 PR(Shortcut 命令),再收集 contributors(Raw API),避免一次性大量 API 调用
|
||||
2. **分页处理**:Issue 和 PR 数量超过单页限制时,用 `--page` 逐页获取
|
||||
3. **时间计算**:ISO 8601 格式解析优先,Unix 时间戳更可靠但 PR 的 `pr_created_unix` 仅部分返回
|
||||
4. **指标计算容错**:样本不足时标注而非报错,新项目可能仅有少量数据
|
||||
5. **评分可按需调整**:新项目无 Release 时,"无近期发布"项自动跳过
|
||||
6. **避免 GIGO**:数据异常时(如极长的响应时间),标注并排除 outlier
|
||||
7. **HTML 生成**:读取 `template.html` → 替换所有 `{{PLACEHOLDER}}` → 写入 `report.html` → 自动 `start`/`open` 打开浏览器
|
||||
8. **禁止创建临时文件**:所有 API 数据在内存中处理,禁止创建 `tmp_data/`、脚本文件、中间 JSON 等任何多余文件。整个流程只输出一个 `report.html`。
|
||||
|
||||
## References
|
||||
|
||||
- [SKILL.md](../SKILL.md) — 工作流和模板总览
|
||||
- [collect-data](../references/collect-data.md) — 数据收集详细说明
|
||||
- [health-metrics](../references/health-metrics.md) — 指标计算和评分规则
|
||||
- [generate-report](../references/generate-report.md) — 报告生成和输出
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
# 收集健康度数据
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
收集项目健康度报告所需的四类数据:Issue、PR、贡献者和项目活动。
|
||||
|
||||
## 命令
|
||||
|
||||
### 步骤 1:收集 Issue 数据
|
||||
|
||||
```bash
|
||||
# 获取未关闭 Issue(用于统计积压)
|
||||
gitlink-cli issue +list --state open --format json
|
||||
|
||||
# 获取已关闭 Issue(用于计算响应时间)
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
```
|
||||
|
||||
Issue 返回的关键字段:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `project_issues_index` | Issue 编号 |
|
||||
| `subject` | Issue 标题 |
|
||||
| `status_id` | 状态:1=新增, 2=正在解决, 3=已解决, 5=关闭 |
|
||||
| `priority_id` | 优先级:1=低, 2=正常, 3=高, 4=紧急 |
|
||||
| `created_at` | 创建时间 (ISO 8601) |
|
||||
| `closed_at` | 关闭时间(仅已关闭 Issue 有此字段) |
|
||||
| `author.login` | 创建者 |
|
||||
| `assigners[]` | 负责人列表 |
|
||||
|
||||
### 步骤 2:收集 PR 数据
|
||||
|
||||
```bash
|
||||
# 获取未合并 PR(用于统计积压)
|
||||
gitlink-cli pr +list --state open --format json
|
||||
|
||||
# 获取已合并 PR(用于计算合并效率)
|
||||
gitlink-cli pr +list --state merged --format json
|
||||
```
|
||||
|
||||
PR 返回的关键字段:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `pull_request_number` | PR 编号 |
|
||||
| `name` (title) | PR 标题 |
|
||||
| `pull_request_status` | 状态:0=open, 1=merged, 2=closed |
|
||||
| `author_login` | 作者 |
|
||||
| `pr_full_time` | 创建时间 (ISO 8601) |
|
||||
| `pr_merged_at` | 合并时间 |
|
||||
| `pr_created_unix` | 创建时间 (Unix timestamp) |
|
||||
|
||||
> ⚠️ `--state` 参数仅影响 `merged_count`/`open_count`/`closed_count` 汇总计数,API 返回的 issues 列表可能包含所有状态的 PR。需按 `pull_request_status` 客户端过滤。
|
||||
|
||||
### 步骤 3:收集贡献者统计
|
||||
|
||||
```bash
|
||||
# 获取贡献者统计(Raw API,无 Shortcut)
|
||||
gitlink-cli api GET /:owner/:repo/contributors --format json
|
||||
```
|
||||
|
||||
返回关键字段:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `author_count` | 总贡献者数 |
|
||||
| `commit_count` | 总 commit 数 |
|
||||
| `commit_count_in_all_branches` | 全部分支的 commit 总数 |
|
||||
| `additions` / `deletions` | 总增删行数 |
|
||||
| `authors[]` | 每位贡献者的明细 |
|
||||
|
||||
每位贡献者(`authors[]`)字段:
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| `login` / `name` | 贡献者 ID 和昵称 |
|
||||
| `commits` | commit 数量 |
|
||||
| `additions` / `deletions` | 增删行数 |
|
||||
|
||||
### 步骤 4:收集项目活动(可选)
|
||||
|
||||
```bash
|
||||
# 获取项目活动 feed
|
||||
gitlink-cli api GET /:owner/:repo/activity --format json
|
||||
```
|
||||
|
||||
用于补充近期事件(issue 创建/关闭、PR 创建/合并的时间线)。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--format` | 否 | 始终建议 `json`,便于 AI 解析 |
|
||||
| `--state` | 否 | Issue: `open`/`closed`;PR: `open`/`merged`/`closed` |
|
||||
| `--page` | 否 | 大量数据时分页获取 |
|
||||
| `--limit` | 否 | 每页条数 |
|
||||
|
||||
## 数据覆盖范围
|
||||
|
||||
数据收集覆盖以下时间范围:
|
||||
|
||||
- **Issue**: 所有未关闭 + 近期已关闭(默认取最近 100 条)
|
||||
- **PR**: 所有未合并 + 近期已合并(默认取最近 100 条)
|
||||
- **Contributors**: 项目全量历史数据
|
||||
- **Activity**: 最近 30 天
|
||||
|
||||
对于大型项目,可通过 `--page` 分页获取更多数据。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 计算响应时间需要 Issue 有 `closed_at` 字段,仅已关闭 Issue 才会返回此字段
|
||||
- PR 合并时间需通过 `pr_full_time` 与当前时间对比估算,或结合 PM `/weekly_issues` API
|
||||
- `contributors` 端点统计基于默认分支(通常为 `master`),不含其他分支
|
||||
- `--owner` / `--repo` 在仓库目录下可自动解析
|
||||
- 时间计算使用 Unix 时间戳(`pr_created_unix`)比解析字符串格式(`pr_full_time`)更可靠
|
||||
|
||||
## References
|
||||
|
||||
- [health-metrics](health-metrics.md) — 收集完成后进行指标计算
|
||||
- [generate-report](generate-report.md) — 生成报告并输出
|
||||
- [gitlink-shared](../../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# 生成并输出健康度报告
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
将计算的指标和评分套用 HTML 模板,生成网页版健康度报告,自动打开浏览器展示。
|
||||
|
||||
## 输出方式(HTML 网页)
|
||||
|
||||
**CRITICAL — 整个流程只允许输出一个文件 `skills/gitlink-health/report.html`,禁止创建任何临时文件、中间文件、缓存目录、脚本文件或额外的 Markdown 文件。**
|
||||
|
||||
### 步骤 1:填充模板
|
||||
|
||||
读取 `skills/gitlink-health/template.html`,将计算后的指标替换所有 `{{PLACEHOLDER}}` 占位符。占位符说明见 [SKILL.md](../SKILL.md#占位符说明)。
|
||||
|
||||
### 步骤 2:写入文件
|
||||
|
||||
将填充后的 HTML 写入 `skills/gitlink-health/report.html`。
|
||||
|
||||
### 步骤 3:自动打开浏览器
|
||||
|
||||
| 平台 | 命令 |
|
||||
|------|------|
|
||||
| Windows | `start skills/gitlink-health/report.html` |
|
||||
| macOS | `open skills/gitlink-health/report.html` |
|
||||
| Linux | `xdg-open skills/gitlink-health/report.html` |
|
||||
|
||||
## 备选输出方式(仅发布 Issue,不创建额外文件)
|
||||
|
||||
### 创建报告 Issue(需认证)
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +create \
|
||||
-t "项目健康度报告 — {DATE}" \
|
||||
-b "<Markdown 格式的完整报告>" \
|
||||
--label 文档
|
||||
```
|
||||
|
||||
> ⚠️ 此为 Write Operation,创建前必须确认用户意图。内容从内存中的计算结果直接生成,不写本地文件。
|
||||
|
||||
## 模板关键占位符
|
||||
|
||||
| 占位符 | 来源 |
|
||||
|--------|------|
|
||||
| `{{OWNER}}` / `{{REPO}}` | git remote 解析 |
|
||||
| `{{DATE}}` | 当前日期 |
|
||||
| `{{SCORE}}` | 综合评分(0-100) |
|
||||
| `{{SCORE_COLOR}}` | #00b894(90+)/#0984e3(70+)/#fdcb6e(50+)/#e17055(30+)/#d63031(<30) |
|
||||
| `{{SCORE_DASH}}` | `(SCORE/100*377) 377` |
|
||||
| `{{GRADE}}` | 优秀 / 良好 / 一般 / 需关注 / 严重 |
|
||||
| `{{DEDUCTION_ROWS}}` | 6 行 `<tr>` 扣分明细 |
|
||||
| `{{OPEN_ISSUES}}` / `{{CLOSED_ISSUES}}` 等 | 统计数据 |
|
||||
| `{{CONTRIBUTOR_ROWS}}` | 每人一行 `<tr>` |
|
||||
| `{{SUGGESTIONS}}` | `<li>` 列表 |
|
||||
|
||||
完整占位符列表参见 [SKILL.md](../SKILL.md).
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **计算**所有指标和评分(参见 [health-metrics](health-metrics.md)),所有数据在内存中处理
|
||||
2. **填充** HTML 模板所有占位符
|
||||
3. **写入** `skills/gitlink-health/report.html`(唯一的输出文件)
|
||||
4. **打开** 浏览器自动展示(`start` / `open` / `xdg-open`)
|
||||
5. 如需持久化,可选发布为 Issue(内容从内存生成,不写本地文件)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 报告的时间范围默认取最近 30 天,用户可指定自定义范围
|
||||
- 若项目无 Release(如新项目),扣分项"无近期发布"不适用,总分自动调整
|
||||
- 贡献者活跃度需结合近 30 天的活动时间线判断
|
||||
- 改进建议应具体、可执行,避免空泛描述
|
||||
- 如果数据量不足(如新项目仅有少量 Issue/PR),在报告中标注"样本量小,仅供参考"
|
||||
|
||||
## References
|
||||
|
||||
- [collect-data](collect-data.md) — 收集项目数据
|
||||
- [health-metrics](health-metrics.md) — 指标计算和评分规则
|
||||
- [full-workflow](../examples/full-workflow.md) — 完整端到端示例
|
||||
- [gitlink-shared](../../gitlink-shared/SKILL.md) — 认证和全局参数
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
# 健康度指标计算
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
基于收集到的数据,计算 Issue 响应时间、PR 合并效率、贡献者活跃度三大指标,并给出综合健康评分。
|
||||
|
||||
## 指标 1:Issue 响应时间
|
||||
|
||||
### 两种评价方式
|
||||
|
||||
Issue 响应时间支持两种计算口径,AI Agent 可根据数据可用性和项目工作流选择:
|
||||
|
||||
| 口径 | 计算方式 | 含义 | 适用场景 |
|
||||
|------|----------|------|----------|
|
||||
| **响应时间** | `resolved_at - created_at` | 从创建到解决的时间 | 衡量团队对 Issue 的实际响应速度 |
|
||||
| **全周期时间** | `closed_at - created_at` | 从创建到正式关闭的时间 | 衡量 Issue 的完整生命周期 |
|
||||
|
||||
**推荐优先使用响应时间**(新增→已解决),因为它反映真实处理效率。全周期时间受"解决后批量关闭"等工作流影响,可能虚高。
|
||||
|
||||
### 计算所需数据
|
||||
|
||||
从 `issue +list --state closed` 返回的 Issue 列表中提取:
|
||||
|
||||
- `created_at` — 创建时间
|
||||
- `updated_at` — 最后更新时间(当无独立 `closed_at`/`resolved_at` 时的降级代理字段)
|
||||
- `status_id` — 状态:1=新增, 2=正在解决, 3=已解决, 5=关闭
|
||||
- `status_name` — 状态名称(辅助判断)
|
||||
|
||||
> **注意**:GitLink API 不直接返回 `closed_at` 或 `resolved_at` 字段。AI Agent 需根据上下文推断:
|
||||
> - 若 Issue 的 `status_id=5`(关闭)且 `updated_at` 在近期批量操作中突变,则 `updated_at` 反映的是关闭时间而非解决时间,应尝试从历史会话或其他来源获取解决时间
|
||||
> - 若项目工作流为"解决即关闭"(单步),则 `updated_at` 同时代表解决和关闭时间,可直接使用
|
||||
|
||||
### 计算公式
|
||||
|
||||
```
|
||||
# 方式 A:响应时间(推荐)
|
||||
time_to_resolve = resolved_at - created_at
|
||||
avg_response = sum(time_to_resolve) / count
|
||||
median_response = sorted(time_to_resolve)[count / 2]
|
||||
|
||||
# 方式 B:全周期时间
|
||||
time_to_close = closed_at - created_at
|
||||
avg_close = sum(time_to_close) / count
|
||||
median_close = sorted(time_to_close)[count / 2]
|
||||
```
|
||||
|
||||
### 阈值
|
||||
|
||||
| 指标 | 优秀 | 良好 | 需改进 |
|
||||
|------|------|------|--------|
|
||||
| 平均响应/关闭时间 | ≤ 2 天 | ≤ 7 天 | > 7 天 |
|
||||
| 中位数响应/关闭时间 | ≤ 1 天 | ≤ 5 天 | > 5 天 |
|
||||
| Issue 积压数 | ≤ 10 | ≤ 20 | > 20 |
|
||||
|
||||
### 时间计算注意
|
||||
|
||||
- 时间字段为 ISO 8601 格式(如 `"2026-06-04 14:45"`),需解析后求差值
|
||||
- 无 `closed_at` 的 Issue(open 状态)不计入响应时间,计入积压统计
|
||||
- 当两种口径结果差异显著时(如响应时间 1.5 天 vs 全周期 15 天),以响应时间为评分依据,在全周期时间处标注"受工作流影响"
|
||||
|
||||
## 指标 2:PR 合并效率
|
||||
|
||||
### 三段时间计算
|
||||
|
||||
PR 合并效率由**三类 PR** 共同决定,需计算**三段时间**:
|
||||
|
||||
| 时间编号 | 名称 | 公式 | 适用对象 |
|
||||
|----------|------|------|----------|
|
||||
| ① | 已合并 PR 平均合并时长 | `avg(merged_at - created_at)` | status=1(已合并) |
|
||||
| ② | 开放 PR 平均等待时长 | `avg(now - created_at)` | status=0(开放中) |
|
||||
| ③ | 加权总平均处理时长 | 见下方公式 | status=1 ∪ status=0 |
|
||||
|
||||
### 计算公式
|
||||
|
||||
```
|
||||
# ① 已合并 PR 平均合并时长
|
||||
time_merged_i = merged_at_i - created_at_i
|
||||
avg_merged = sum(time_merged_i) / merged_count
|
||||
|
||||
# ② 开放 PR 平均等待时长
|
||||
time_open_i = now - created_at_i
|
||||
avg_open = sum(time_open_i) / open_count
|
||||
|
||||
# ③ 加权总平均处理时长
|
||||
total_count = merged_count + open_count
|
||||
weight_merged = merged_count / total_count
|
||||
weight_open = open_count / total_count
|
||||
weighted_total = weight_merged * avg_merged + weight_open * avg_open
|
||||
```
|
||||
|
||||
### 状态分类
|
||||
|
||||
| 状态 | 含义 | 是否计入 | 计入哪类 |
|
||||
|------|------|---------|---------|
|
||||
| `pull_request_status=1` | 已合并 | ✅ | ①已合并 |
|
||||
| `pull_request_status=0` | 开放中 | ✅ | ②开放等待 |
|
||||
| `pull_request_status=2` | 已关闭(未合并) | ❌ | 不参与时间计算,但参与合并率分母 |
|
||||
|
||||
> **设计依据**:
|
||||
> - 已合并 PR 已有"完成时间"(merged_at),用真实合并耗时
|
||||
> - 开放 PR 尚未合并,"等待时长"= 从创建到当前(持续增长中),反映积压压力
|
||||
> - 加权平均综合体现项目处理 PR 的整体节奏,比单一指标更准确
|
||||
|
||||
### 合并率(独立指标)
|
||||
|
||||
```
|
||||
merge_rate = merged_count / (merged_count + closed_count) × 100%
|
||||
```
|
||||
|
||||
仅使用「已合并」与「已关闭未合并」作为分母,开放 PR 不参与。
|
||||
|
||||
### 阈值
|
||||
|
||||
| 指标 | 优秀 | 良好 | 需改进 |
|
||||
|------|------|------|--------|
|
||||
| ① 已合并 PR 平均合并时长 | ≤ 1 小时 | ≤ 1 天 | > 1 天 |
|
||||
| ② 开放 PR 平均等待时长 | ≤ 1 天 | ≤ 7 天 | > 7 天 |
|
||||
| **③ 加权总平均处理时长** | **≤ 6 小时** | **≤ 2 天** | **> 2 天** |
|
||||
| PR 积压数 | ≤ 3 | ≤ 10 | > 10 |
|
||||
| 合并率 | ≥ 90% | ≥ 70% | < 70% |
|
||||
|
||||
> **可调阈值**:此表为默认值。不同项目工作流差异大(如 fork 端预审型 vs 上游社区 PR 型),可在 `health-metrics.md` 中按项目特征调整。
|
||||
|
||||
### 数据可用性
|
||||
|
||||
| 字段 | 来源命令 | 路径 |
|
||||
|------|---------|------|
|
||||
| `created_at` | `pr +list` | `data.issues[].pr_full_time` 或 `pr_created_unix` |
|
||||
| `merged_at` | `pr +view --id <pull_request_number>` | `data.pull_request.merged_at` |
|
||||
|
||||
> **绝对禁止**:用「当前时间 - 创建时间」估算已合并 PR 的合并时间。已合并 PR 必须用真实的 `merged_at`。
|
||||
> 开放 PR 用「当前时间 - 创建时间」是允许的(且必要),因为它们没有合并时间点,等待时长反映积压。
|
||||
|
||||
### 数据可用性
|
||||
|
||||
PR 合并时间**只与 PR 自身时间相关**(创建时间 + 合并时间),**与当前时间无关**。
|
||||
|
||||
实际数据源:
|
||||
|
||||
| 字段 | 来源命令 | 路径 |
|
||||
|------|---------|------|
|
||||
| `created_at` | `pr +list --state merged` | `data.issues[].pr_full_time` 或 `pr_created_unix` |
|
||||
| `merged_at` | `pr +view --id <pull_request_id>` | `data.pull_request.merged_at` |
|
||||
|
||||
**注意**:`merged_at` 只在 `pr +view` 单个 PR 详情中暴露,列表接口不返回。需要对每个已合并 PR 调用一次详情接口。
|
||||
|
||||
> **绝对禁止**:使用「当前时间 - 创建时间」作为合并时间估算。这种做法会把开放中或近期合并的 PR 误判为"超长合并时间",与 PR 合并效率的真实含义相悖。
|
||||
|
||||
## 指标 3:贡献者活跃度
|
||||
|
||||
### 计算所需数据
|
||||
|
||||
从 `GET /:owner/:repo/contributors` 返回:
|
||||
|
||||
- `authors[].login` — 贡献者登录名
|
||||
- `authors[].commits` — 提交数
|
||||
- `authors[].additions / deletions` — 增删行数
|
||||
|
||||
### 活跃度分级
|
||||
|
||||
| 级别 | 图标 | 条件 |
|
||||
|------|------|------|
|
||||
| 高频活跃 | 🔥 | 近 30 天 commits ≥ 5 或 PRs ≥ 2 |
|
||||
| 正常活跃 | 🟢 | 近 30 天 commits ≥ 1 或 PRs ≥ 1 |
|
||||
| 低频活跃 | 🟡 | 近 30 天无 commit 但有 Issue 活动 |
|
||||
| 不活跃 | ⚪ | 近 60 天无任何贡献活动 |
|
||||
|
||||
### 贡献者集中度
|
||||
|
||||
```
|
||||
top_contributor_share = max(authors[].commits) / total_commits × 100%
|
||||
```
|
||||
|
||||
集中度 > 70% 视为过度集中(bus factor 低,有单点风险)。
|
||||
|
||||
## 综合健康评分(100 分制)
|
||||
|
||||
### 计分规则
|
||||
|
||||
起始分 **100 分**,逐项扣分。核心三指标(Issue 响应时间、PR 合并效率、贡献者活跃度)占 75 分,辅助指标占 25 分。
|
||||
|
||||
### 核心指标(75 分)
|
||||
|
||||
| 扣分项 | 扣分 | 触发条件 | 说明 |
|
||||
|--------|------|----------|------|
|
||||
| Issue 响应慢 | -25 | `avg_response > 7天`(使用响应时间口径,即新增→已解决) | Issue 平均解决时间超过一周 |
|
||||
| PR 合并慢 | -25 | `avg_merge > 1天` | PR 平均合并时间超过一天 |
|
||||
| 贡献者活跃度低 | -25 | `active_contributors < 2` 或 `top_contributor_share > 70%` | 活跃贡献者过少或过度依赖单一贡献者 |
|
||||
|
||||
### 辅助指标(25 分)
|
||||
|
||||
| 扣分项 | 扣分 | 触发条件 | 说明 |
|
||||
|--------|------|----------|------|
|
||||
| Issue 积压 | -10 | `open Issues > 20` | 待处理 Issue 数量过多 |
|
||||
| PR 积压 | -10 | `open PRs > 10` | 待合并 PR 数量过多 |
|
||||
| 无近期发布 | -5 | `latest_release > 30天` | 近 30 天无新发布 |
|
||||
|
||||
### 评分等级
|
||||
|
||||
| 分数 | 等级 | 图标 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 90-100 | 优秀 | 🟢 | 项目运转非常健康 |
|
||||
| 70-89 | 良好 | 🔵 | 整体正常,有小问题 |
|
||||
| 50-69 | 一般 | 🟡 | 需要关注多项指标 |
|
||||
| 30-49 | 需关注 | 🟠 | 存在明显瓶颈 |
|
||||
| 0-29 | 严重 | 🔴 | 需要立即干预 |
|
||||
|
||||
### 改进建议生成
|
||||
|
||||
根据扣分项自动生成改进建议:
|
||||
|
||||
| 扣分项 | 改进建议 |
|
||||
|--------|----------|
|
||||
| Issue 积压 | 建议安排 Issue Triage,优先处理高优先级 Issue |
|
||||
| PR 积压 | 建议增加 Code Review 资源,缩短 PR 等待时间 |
|
||||
| Issue 响应慢 | 建议建立 Issue 处理 SLA,落实责任人 |
|
||||
| PR 合并慢 | 建议设 PR 合并时效目标(如 48 小时内) |
|
||||
| 贡献者集中 | 建议鼓励多人参与核心模块,避免单点风险 |
|
||||
| 无近期发布 | 建议建立定期发布节奏(如每 2 周发一次) |
|
||||
|
||||
## 输出格式
|
||||
|
||||
计算完成后,整理为结构化数据供模板填充:
|
||||
|
||||
```
|
||||
综合评分: 70/100(良好 🟡)
|
||||
核心扣分: Issue 响应慢 -25 (avg 8天), 贡献者活跃度低 -25 (仅1位活跃)
|
||||
辅助扣分: Issue 积压 -10 (当前 25 个)
|
||||
|
||||
Issue 响应时间:
|
||||
平均 4.2 天, 中位数 2.1 天, 积压 25 个
|
||||
|
||||
PR 合并效率:
|
||||
平均 1.8 天, 中位数 0.9 天, 积压 12 个, 合并率 85%
|
||||
|
||||
贡献者活跃度:
|
||||
总贡献者 5, 总 commits 247
|
||||
前三: mengcheng(120), zzx-coder(65), wbtiger(40)
|
||||
集中度: mengcheng 占 48.6%(正常)
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [collect-data](collect-data.md) — 数据收集步骤
|
||||
- [generate-report](generate-report.md) — 报告生成和输出
|
||||
- [SKILL.md](../SKILL.md) — 评分规则速查表
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>项目健康度报告 — {{OWNER}}/{{REPO}}</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f0f2f5;
|
||||
--card-bg: #ffffff;
|
||||
--text: #1a1a2e;
|
||||
--text-secondary: #6b7280;
|
||||
--border: #e5e7eb;
|
||||
--green: #10b981;
|
||||
--blue: #3b82f6;
|
||||
--yellow: #f59e0b;
|
||||
--orange: #e17055;
|
||||
--red: #ef4444;
|
||||
--header-bg: #0f172a;
|
||||
--accent: #8b5cf6;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
||||
color: #fff;
|
||||
padding: 52px 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 56px;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header::before {
|
||||
content: ''; position: absolute; top: -80px; right: -60px;
|
||||
width: 300px; height: 300px; border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(225,112,85,0.12) 0%, transparent 70%);
|
||||
}
|
||||
.header-left { flex: 1; min-width: 280px; position: relative; z-index: 1; }
|
||||
.header-left .project-icon { font-size: 40px; margin-bottom: 8px; }
|
||||
.header-left h1 { font-size: 30px; font-weight: 800; margin-bottom: 4px; letter-spacing: -0.5px; }
|
||||
.header-left .subtitle { font-size: 15px; color: #94a3b8; font-weight: 500; }
|
||||
.header-left .meta { font-size: 13px; color: #64748b; margin-top: 10px; display: flex; gap: 16px; flex-wrap: wrap; }
|
||||
.header-left .alert-tags { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
|
||||
.header-left .alert-tags span {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 5px 12px; border-radius: 6px;
|
||||
font-size: 12px; font-weight: 600;
|
||||
}
|
||||
.alert-red { background: rgba(239,68,68,0.18); color: #ef4444; }
|
||||
.alert-orange { background: rgba(225,112,85,0.18); color: #e17055; }
|
||||
|
||||
.score-ring-wrap { flex-shrink: 0; position: relative; z-index: 1; text-align: center; }
|
||||
.score-ring { width: 150px; height: 150px; position: relative; display: inline-block; }
|
||||
.score-ring svg { transform: rotate(-90deg); }
|
||||
.score-ring .bg-circle { fill: none; stroke: #1e293b; stroke-width: 12; }
|
||||
.score-ring .fg-circle {
|
||||
fill: none; stroke-width: 12; stroke-linecap: round;
|
||||
filter: drop-shadow(0 0 8px var(--glow-color));
|
||||
}
|
||||
.score-ring .score-text {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; flex-direction: column;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.score-ring .score-text .num { font-size: 44px; font-weight: 800; line-height: 1; }
|
||||
.score-ring .score-text .label { font-size: 13px; color: #94a3b8; margin-top: 2px; }
|
||||
.grade-badge {
|
||||
display: inline-block; padding: 5px 16px; border-radius: 20px;
|
||||
font-size: 13px; font-weight: 700; margin-top: 8px; letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.main { max-width: 1200px; margin: 0 auto; padding: 36px 24px; }
|
||||
.section { margin-bottom: 36px; }
|
||||
.section-title {
|
||||
font-size: 20px; font-weight: 700; margin-bottom: 18px;
|
||||
padding-bottom: 10px; border-bottom: 2px solid var(--border);
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.section-title .icon { font-size: 24px; }
|
||||
|
||||
.deduction-table { width: 100%; border-collapse: collapse; background: var(--card-bg); border-radius: 14px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.deduction-table th, .deduction-table td { padding: 14px 22px; text-align: left; font-size: 14px; }
|
||||
.deduction-table th { background: #f8fafc; font-weight: 700; color: var(--text-secondary); font-size: 12px; text-transform: uppercase; letter-spacing: 0.8px; }
|
||||
.deduction-table td { border-top: 1px solid var(--border); }
|
||||
.deduction-table .status-icon { margin-right: 6px; font-size: 16px; }
|
||||
.deduct-badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-weight: 700; font-size: 13px; }
|
||||
.deduct-ok { background: #d1fae5; color: #065f46; }
|
||||
.deduct-warn { background: #fef3c7; color: #92400e; }
|
||||
.deduct-bad { background: #fee2e2; color: #991b1b; }
|
||||
.deduction-table tr.row-warn { background: #fffbeb; }
|
||||
.deduction-table tr.row-bad { background: #fef2f2; }
|
||||
|
||||
.kpi-banner {
|
||||
background: linear-gradient(135deg, #fee2e2 0%, #fff5f5 100%);
|
||||
border: 1px solid #fecaca; border-radius: 10px;
|
||||
padding: 14px 20px; margin-bottom: 20px;
|
||||
font-size: 14px; font-weight: 600; color: #991b1b;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.kpi-banner .kpi-icon { font-size: 22px; }
|
||||
|
||||
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); gap: 22px; }
|
||||
.card {
|
||||
background: var(--card-bg); border-radius: 14px;
|
||||
padding: 26px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
|
||||
.card h3 { font-size: 17px; font-weight: 700; margin-bottom: 18px; display: flex; align-items: center; gap: 8px; }
|
||||
.card h3 .card-icon { font-size: 22px; }
|
||||
.card table { width: 100%; border-collapse: collapse; }
|
||||
.card table td { padding: 9px 0; font-size: 14px; border-bottom: 1px solid var(--border); }
|
||||
.card table td:last-child { text-align: right; font-weight: 700; }
|
||||
.card table tr:last-child td { border-bottom: none; }
|
||||
.metric-highlight { display: inline-block; padding: 2px 8px; border-radius: 6px; font-size: 12px; margin-left: 4px; font-weight: 700; }
|
||||
.hl-good { background: #d1fae5; color: #065f46; }
|
||||
.hl-warn { background: #fef3c7; color: #92400e; }
|
||||
.hl-bad { background: #fee2e2; color: #991b1b; }
|
||||
|
||||
.priority-bar { display: flex; height: 10px; border-radius: 5px; overflow: hidden; margin: 14px 0 10px; }
|
||||
.priority-bar span { display: block; height: 100%; }
|
||||
.pg-urgent { background: var(--red); }
|
||||
.pg-high { background: var(--orange); }
|
||||
.pg-normal { background: var(--blue); }
|
||||
.pg-low { background: var(--green); }
|
||||
.priority-legend { display: flex; gap: 18px; flex-wrap: wrap; font-size: 13px; color: var(--text-secondary); }
|
||||
.priority-legend span { display: flex; align-items: center; gap: 5px; }
|
||||
.priority-legend .dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
|
||||
|
||||
/* Podium */
|
||||
.podium-section { background: var(--card-bg); border-radius: 14px; padding: 30px 24px 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||||
.podium-container { display: flex; align-items: flex-end; justify-content: center; gap: 20px; margin: 20px 0 32px; }
|
||||
.podium-spot { display: flex; flex-direction: column; align-items: center; min-width: 110px; }
|
||||
.podium-spot .avatar-area { position: relative; margin-bottom: 8px; }
|
||||
.podium-spot .crown { font-size: 26px; position: absolute; top: -30px; left: 50%; transform: translateX(-50%); animation: float 2s ease-in-out infinite; }
|
||||
.podium-spot .avatar { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: 800; color: #fff; }
|
||||
.podium-spot .name { font-weight: 700; font-size: 14px; margin: 6px 0 2px; text-align: center; }
|
||||
.podium-spot .stat { font-size: 12px; color: var(--text-secondary); text-align: center; }
|
||||
.podium-block { width: 100%; border-radius: 8px 8px 0 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; font-weight: 800; }
|
||||
.podium-block .rank { font-size: 28px; line-height: 1.2; }
|
||||
|
||||
.podium-champion { order: 2; }
|
||||
.podium-champion .podium-block { background: linear-gradient(180deg, #fbbf24 0%, #f59e0b 100%); height: 120px; box-shadow: 0 4px 20px rgba(245,158,11,0.4); }
|
||||
.podium-champion .avatar { background: linear-gradient(135deg, #fbbf24, #f59e0b); width: 76px; height: 76px; font-size: 32px; box-shadow: 0 0 0 4px rgba(245,158,11,0.3); }
|
||||
.podium-champion .crown { color: #f59e0b; font-size: 30px; top: -34px; animation-delay: 0s; }
|
||||
|
||||
.podium-runner { order: 1; }
|
||||
.podium-runner .podium-block { background: linear-gradient(180deg, #cbd5e1 0%, #94a3b8 100%); height: 90px; box-shadow: 0 4px 16px rgba(148,163,184,0.3); }
|
||||
.podium-runner .avatar { background: linear-gradient(135deg, #cbd5e1, #94a3b8); box-shadow: 0 0 0 4px rgba(148,163,184,0.3); }
|
||||
.podium-runner .crown { color: #94a3b8; animation-delay: 0.3s; }
|
||||
|
||||
.podium-third { order: 3; }
|
||||
.podium-third .podium-block { background: linear-gradient(180deg, #e8a87c 0%, #cd7f32 100%); height: 64px; box-shadow: 0 4px 12px rgba(205,127,50,0.35); }
|
||||
.podium-third .avatar { background: linear-gradient(135deg, #e8a87c, #cd7f32); box-shadow: 0 0 0 4px rgba(205,127,50,0.3); }
|
||||
.podium-third .crown { color: #cd7f32; animation-delay: 0.6s; }
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateX(-50%) translateY(0); }
|
||||
50% { transform: translateX(-50%) translateY(-4px); }
|
||||
}
|
||||
|
||||
.other-contributors { border-top: 1px solid var(--border); padding-top: 16px; }
|
||||
.other-contributors .list-header { font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 10px; }
|
||||
.contributor-row { display: flex; align-items: center; gap: 12px; padding: 8px 12px; border-radius: 8px; transition: background 0.1s; }
|
||||
.contributor-row:hover { background: #f8fafc; }
|
||||
.contributor-row .rank-num { width: 28px; text-align: center; font-weight: 700; font-size: 14px; color: var(--text-secondary); }
|
||||
.contributor-row .avatar-small { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; font-weight: 700; color: #fff; flex-shrink: 0; }
|
||||
.contributor-row .info { flex: 1; min-width: 0; }
|
||||
.contributor-row .info .name { font-weight: 600; font-size: 14px; }
|
||||
.contributor-row .info .detail { font-size: 12px; color: var(--text-secondary); }
|
||||
|
||||
.suggestions { list-style: none; display: grid; gap: 12px; }
|
||||
.suggestions li {
|
||||
background: var(--card-bg); border-radius: 10px; padding: 16px 20px 16px 48px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.04); font-size: 14px;
|
||||
position: relative; border-left: 4px solid var(--accent);
|
||||
}
|
||||
.suggestions li .sug-icon { position: absolute; left: 14px; top: 15px; font-size: 18px; }
|
||||
|
||||
.data-note {
|
||||
background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 8px;
|
||||
padding: 12px 18px; margin-top: 20px; font-size: 12px; color: #166534;
|
||||
}
|
||||
|
||||
.footer { text-align: center; padding: 28px; font-size: 12px; color: #b2bec3; }
|
||||
.footer a { color: var(--accent); text-decoration: none; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header { padding: 32px 24px; flex-direction: column; text-align: center; }
|
||||
.podium-container { gap: 12px; }
|
||||
.podium-spot { min-width: 80px; }
|
||||
.podium-champion .podium-block { height: 90px; }
|
||||
.podium-runner .podium-block { height: 68px; }
|
||||
.podium-third .podium-block { height: 50px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<div class="header-left">
|
||||
<div class="project-icon">🏠</div>
|
||||
<h1>{{OWNER}} / {{REPO}}</h1>
|
||||
<div class="subtitle">📊 项目健康度报告</div>
|
||||
<div class="meta">
|
||||
<span>📅 {{DATE}}</span>
|
||||
<span>⏳ 统计周期:{{PERIOD_DAYS}} 天</span>
|
||||
{{META_EXTRAS}}
|
||||
</div>
|
||||
<div class="alert-tags">
|
||||
{{ALERT_TAGS}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="score-ring-wrap">
|
||||
<div class="score-ring">
|
||||
<svg width="150" height="150" viewBox="0 0 150 150">
|
||||
<circle class="bg-circle" cx="75" cy="75" r="64"/>
|
||||
<circle class="fg-circle" cx="75" cy="75" r="64" stroke="{{SCORE_COLOR}}" style="--glow-color:{{SCORE_COLOR}}" stroke-dasharray="{{SCORE_DASH}}" stroke-dashoffset="0"/>
|
||||
</svg>
|
||||
<div class="score-text">
|
||||
<span class="num" style="color:{{SCORE_COLOR}}">{{SCORE}}</span>
|
||||
<span class="label">/ 100</span>
|
||||
<span class="grade-badge" style="background:{{SCORE_COLOR}}22;color:{{SCORE_COLOR}}">{{GRADE}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<!-- Deduction Detail -->
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
<span class="icon">📋</span> 扣分明细
|
||||
</div>
|
||||
<table class="deduction-table">
|
||||
<thead>
|
||||
<tr><th>指标</th><th>实际值</th><th>扣分</th><th>评定</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{DEDUCTION_ROWS}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Metric Cards -->
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
<span class="icon">📊</span> 关键指标
|
||||
</div>
|
||||
|
||||
{{KPI_BANNER}}
|
||||
|
||||
<div class="cards">
|
||||
{{ISSUE_CARD}}
|
||||
{{PR_CARD}}
|
||||
</div>
|
||||
|
||||
{{DATA_NOTE}}
|
||||
</div>
|
||||
|
||||
<!-- Podium -->
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
<span class="icon">🏆</span> 贡献者活跃度{{PODIUM_SUBTITLE}}
|
||||
</div>
|
||||
<div class="podium-section">
|
||||
<div class="podium-container">
|
||||
{{PODIUM}}
|
||||
<!-- 每位 podium-spot 格式:
|
||||
<div class="podium-spot podium-champion">
|
||||
<div class="avatar-area">
|
||||
<div class="crown">♛</div>
|
||||
<div class="avatar">W</div>
|
||||
</div>
|
||||
<div class="name">用户名</div>
|
||||
<div class="stat">N PRs · M Issues · 🔥 高频</div>
|
||||
<!-- 注意:仅 podium 前三名标注活跃度(🔥高频/🟢正常/🟡低频),其他贡献者不标注 -->
|
||||
<div class="podium-block"><span class="rank">🥇</span></div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="other-contributors">
|
||||
<div class="list-header">📋 其他贡献者{{OTHER_CONTRIBUTORS_HEADER}}</div>
|
||||
{{OTHER_CONTRIBUTORS}}
|
||||
<!-- 每条 contributor-row 格式:
|
||||
<div class="contributor-row">
|
||||
<span class="rank-num">#4</span>
|
||||
<div class="avatar-small" style="background:...;">X</div>
|
||||
<div class="info">
|
||||
<span class="name">用户名</span>
|
||||
<span class="detail">N PRs · M Issues</span>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
{{MORE_CONTRIBUTORS_HINT}}
|
||||
</div>
|
||||
|
||||
<div style="margin-top:18px;padding-top:14px;border-top:1px solid var(--border);font-size:13px;color:var(--text-secondary);text-align:center;">
|
||||
{{CONTRIBUTOR_SUMMARY}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Suggestions -->
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
<span class="icon">💡</span> 改进建议
|
||||
</div>
|
||||
<ul class="suggestions">
|
||||
{{SUGGESTIONS}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
{{FOOTER}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
# gitlink-issue-triage
|
||||
|
||||
> GitLink Issue 自动分类 Skill — 让 AI Agent 帮你分诊堆积如山的 Issue
|
||||
|
||||
[](./SKILL.md)
|
||||
[](https://claude.com/claude-code)
|
||||
|
||||
## 🎯 这是什么?
|
||||
|
||||
`gitlink-issue-triage` 是基于 [gitlink-cli](../../README.md) 的 **AI Agent Skill**,专门用于:
|
||||
|
||||
- 📥 **批量分诊** 未分类的 GitLink Issue
|
||||
- 🏷️ **自动打标签** (bug / feature / question / ...)
|
||||
- ⚡ **判定优先级** (urgent / high / normal / low)
|
||||
- 👤 **建议指派人** (基于 @mention 和活跃贡献者)
|
||||
- 🔗 **关联相似 Issue** (识别重复、相关历史)
|
||||
- 📊 **生成审计报告** (JSON + 表格,可追溯)
|
||||
|
||||
适合**所有 Issue 堆积严重**的开源项目或团队仓库。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 前置条件
|
||||
|
||||
1. 已安装 `gitlink-cli`(参考 [主 README](../../README.md#安装与快速上手))
|
||||
2. 已完成认证(`gitlink-cli auth login`)
|
||||
3. 在目标仓库目录下(自动解析 owner/repo)或显式指定 `--owner --repo`
|
||||
|
||||
### 5 分钟体验
|
||||
|
||||
向 AI Agent(如 Claude Code)说:
|
||||
|
||||
> "帮我用 gitlink-issue-triage 分析 owner/repo 仓库中所有 open 状态的 Issue,生成报告后等我确认。"
|
||||
|
||||
AI 会:
|
||||
|
||||
1. 拉取 Issue 列表
|
||||
2. 逐个分析(应用规则 + 语义判断)
|
||||
3. 展示表格报告
|
||||
4. 等你确认后才应用变更
|
||||
|
||||
---
|
||||
|
||||
## 📁 Skill 结构
|
||||
|
||||
```
|
||||
gitlink-issue-triage/
|
||||
├── README.md # 本文件
|
||||
├── SKILL.md # AI Agent 读取的主入口
|
||||
├── references/
|
||||
│ ├── gitlink-issue-triage-analyze.md # 分析算法详解
|
||||
│ └── gitlink-issue-triage-apply.md # 应用变更手册
|
||||
└── examples/
|
||||
├── triage-batch-workflow.md # 端到端批量分类示例
|
||||
└── triage-single-issue.md # 单 Issue 深度分析示例
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧠 分类规则一览
|
||||
|
||||
完整规则见 [SKILL.md §4](./SKILL.md#4-分类决策规则核心算法),摘要:
|
||||
|
||||
| 维度 | 决策依据 |
|
||||
|------|---------|
|
||||
| **类型(tracker)** | 关键词匹配(bug/错误/crash → bug;建议/希望 → feature) |
|
||||
| **优先级** | 严重度信号(线上/紧急 → urgent;阻塞 → high) |
|
||||
| **标签** | 仓库已有标签的语义匹配 |
|
||||
| **指派人** | 正文 @mention 优先;否则不自动指派 |
|
||||
| **关联 Issue** | 标题关键词 Jaccard 相似度 ≥ 0.4 |
|
||||
|
||||
**冲突解决**:标题优先于正文;多命中时 `bug > duplicate > feature > question > doc > support`。
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 安全设计
|
||||
|
||||
| 机制 | 说明 |
|
||||
|------|------|
|
||||
| ✅ Dry-run 默认 | 分析阶段不调用任何写 API |
|
||||
| ✅ 双重确认 | 应用变更前必须表格展示 + 用户同意 |
|
||||
| ✅ 不自动关闭 | 即使是 duplicate 也只评论建议 |
|
||||
| ✅ 字段快照 | 每个变更保留原始值,支持回滚 |
|
||||
| ✅ 批次上限 | 单批 ≤ 50 个,超出强制分批 |
|
||||
|
||||
---
|
||||
|
||||
## 🤖 AI Agent 兼容性
|
||||
|
||||
已在以下 Agent 平台验证:
|
||||
|
||||
- ✅ **Claude Code** — 主要验证目标,所有示例均可执行
|
||||
- ✅ **Cursor** — 通过 SKILL.md markdown 协议兼容
|
||||
- ✅ **OpenAI Code** — 通过 references/ 文档兼容
|
||||
|
||||
详见 [AI Agent 测试报告](../../doc/issue-triage-agent-test.md)。
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [SKILL.md — AI Agent 主入口](./SKILL.md)
|
||||
- [分析算法详解](./references/gitlink-issue-triage-analyze.md)
|
||||
- [应用变更手册](./references/gitlink-issue-triage-apply.md)
|
||||
- [批量工作流示例](./examples/triage-batch-workflow.md)
|
||||
- [单 Issue 分析示例](./examples/triage-single-issue.md)
|
||||
- [上游 Skill: gitlink-issue](../gitlink-issue/SKILL.md)
|
||||
- [共享规则: gitlink-shared](../gitlink-shared/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
**Q: 必须用 AI Agent 吗?人能用吗?**
|
||||
A: 当然可以。SKILL.md 中的工作流对人类也是清晰的 SOP,你可以手动按步骤执行 gitlink-cli 命令。
|
||||
|
||||
**Q: 规则会误判吗?**
|
||||
A: 会。规则是启发式,复杂 Issue 需要 AI 语义判断或人工复核。所有"非规则决策"会在报告中高亮。
|
||||
|
||||
**Q: 支持自定义规则吗?**
|
||||
A: 当前版本规则内嵌在 SKILL.md,未来版本会支持外部 YAML 配置。
|
||||
|
||||
**Q: 与 GitHub Actions 的类似机器人有何不同?**
|
||||
A: 本 Skill 是 **Agent-driven**(按需触发、人在环路),不是 **Event-driven**(自动触发、可能误判)。适合需要人工监督的高质量项目。
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
继承 gitlink-cli 的 [MulanPSL-2.0](../../LICENSE)。
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
---
|
||||
name: gitlink-issue-triage
|
||||
version: 1.0.0
|
||||
description: "Issue 自动分类(Issue Triage):根据 Issue 标题与正文,自动判定类型(bug/feature/question 等)、优先级、建议标签与指派人,并生成可审计的分析报告。当用户需要对一批未分类 Issue 自动打标签、分配负责人、关联相似 Issue 时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli issue --help"
|
||||
---
|
||||
|
||||
# gitlink-issue-triage(Issue 自动分类)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有"应用"动作(apply)默认 dry-run;只有用户明确确认后才执行写入。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
|
||||
> **前置依赖:** 先阅读 [`../gitlink-issue/SKILL.md`](../gitlink-issue/SKILL.md) 了解 Issue 基础操作和字段映射。
|
||||
|
||||
---
|
||||
|
||||
## 1. 这个 Skill 做什么?
|
||||
|
||||
`gitlink-issue-triage` 是一个 **AI Agent 驱动的 Issue 自动分类工作流**,解决开源/协作项目中常见的"Issue 堆积无人分诊"问题:
|
||||
|
||||
- 📥 **批量拉取未分类 Issue**(`tracker_id` 缺失、无标签、无 assignee)
|
||||
- 🧠 **基于内容判定**:类型(bug/feature/...)、优先级(low/normal/high/urgent)、建议标签
|
||||
- 👤 **指派建议**:基于关键词匹配仓库内活跃贡献者
|
||||
- 🔗 **关联 Issue**:识别重复或相关 Issue,附在评论中
|
||||
- 📊 **输出结构化报告**(JSON),便于人工复核与审计
|
||||
- ✅ **Dry-run 优先**:所有写入操作默认预览,确认后才落地
|
||||
|
||||
---
|
||||
|
||||
## 2. 工作流总览
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ 1. 拉取未分类 Issue 列表 │ issue +list --state open
|
||||
└────────────┬────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 2. 对每个 Issue 执行分析(AI + 规则) │
|
||||
│ - 关键词匹配 → tracker_id │
|
||||
│ - 严重度信号 → priority_id │
|
||||
│ - 标签建议 → issue_tag_ids │
|
||||
│ - 活跃贡献者 → assigned_to_id │
|
||||
│ - 文本相似度 → related issues │
|
||||
└────────────────┬─────────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 3. 生成分析报告(JSON) │
|
||||
│ { issue_number, decisions, confidence }│
|
||||
└────────────────┬─────────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 4. 用户确认 → 应用变更 │
|
||||
│ issue +update / +label-add / +comment │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Shortcuts 与 Raw API 速查
|
||||
|
||||
本 Skill 复用 gitlink-cli 已有命令,**不新增 shortcut**,确保单一可信源。
|
||||
|
||||
### 3.1 读操作(只读,可放心使用)
|
||||
|
||||
| 命令 | 用途 |
|
||||
|------|------|
|
||||
| `issue +list --state open --format json` | 获取待分类 Issue 列表 |
|
||||
| `issue +view --number <N> --format json` | 获取 Issue 详情(标题/正文/标签) |
|
||||
| `issue +label-list --number <N> --format json` | 查看 Issue 当前标签 |
|
||||
| `api GET /v1/:owner/:repo/issue_tags.json` | 获取仓库可用标签(name→id 映射) |
|
||||
| `api GET /v1/:owner/:repo/issue_assigners.json` | 获取可指派用户列表 |
|
||||
| `api GET /users/:login` | login → user_id 解析 |
|
||||
|
||||
### 3.2 写操作(默认 dry-run,确认后执行)
|
||||
|
||||
| 命令 | 用途 |
|
||||
|------|------|
|
||||
| `issue +update --number <N> --state <s>` | 改状态(如 in-progress) |
|
||||
| `issue +label-add --number <N> --labels "<csv>"` | 加标签 |
|
||||
| `issue +comment --number <N> --body "<text>"` | 评论(关联 Issue 链接、分析摘要) |
|
||||
| `issue +batch-label --label <name> --numbers <csv>` | 批量改 tracker |
|
||||
|
||||
---
|
||||
|
||||
## 4. 分类决策规则(核心算法)
|
||||
|
||||
> 以下规则同时给 AI Agent 和人类审阅者参考。AI Agent 应**优先**遵循规则,对规则无法覆盖的情况使用语义判断。
|
||||
|
||||
### 4.1 类型(tracker_id)决策
|
||||
|
||||
| 关键词(标题或正文,大小写不敏感) | tracker_id | 说明 |
|
||||
|----------------------------------|------------|------|
|
||||
| `bug`, `错误`, `失败`, `崩溃`, `异常`, `报错`, `不能`, `无法`, `crash`, `error`, `exception` | 1 (bug) | 缺陷报告 |
|
||||
| `feature`, `希望`, `建议`, `新增`, `支持`, `能否添加`, `enhancement`, `proposal` | 2 (feature) | 功能请求 |
|
||||
| `怎么`, `如何`, `哪里`, `?`, `?`, `question`, `文档`, `help`, `请问` | 7 (question) | 求助/疑问 |
|
||||
| `重复`, `duplicate`, `已有`, `same as` | 6 (duplicate) | 重复 Issue |
|
||||
| `文档`, `README`, `教程`, `doc`, `typo`, `拼写` | 4 (doc) | 文档类 |
|
||||
| `支持`, `求助`, `support`, `咨询` | 3 (support) | 支持请求 |
|
||||
|
||||
**冲突解决**:标题命中优先于正文命中;多个命中时优先级 `bug > duplicate > feature > question > doc > support`。
|
||||
|
||||
### 4.2 优先级(priority_id)决策
|
||||
|
||||
| 信号 | priority_id |
|
||||
|------|-------------|
|
||||
| 含 `紧急`, `urgent`, `ASAP`, `线上`, `production`, `数据丢失`, `安全`, `security`, `CVE` | 4 (urgent) |
|
||||
| 含 `重要`, `阻塞`, `block`, `无法工作`, `完全不能用`, `high` | 3 (high) |
|
||||
| 默认(无强信号) | 2 (normal) |
|
||||
| 含 `minor`, `小问题`, `建议`, `nice to have`, `low` | 1 (low) |
|
||||
|
||||
### 4.3 标签建议(issue_tag_ids)
|
||||
|
||||
1. 调用 `GET /v1/:owner/:repo/issue_tags.json` 获取仓库已有标签
|
||||
2. 根据分类结果匹配语义相近的标签:
|
||||
- tracker=bug → 优先匹配 `缺陷`/`bug`
|
||||
- tracker=feature → 优先匹配 `功能`/`enhancement`
|
||||
- 优先级=urgent → 加上 `紧急`/`urgent`(如存在)
|
||||
3. 若仓库无对应标签,**跳过标签步骤**,仅在报告中提示
|
||||
|
||||
### 4.4 指派人(assigned_to_id)建议
|
||||
|
||||
1. 调用 `GET /v1/:owner/:repo/issue_assigners.json` 获取可指派列表
|
||||
2. 若 Issue 正文中 `@username`,优先指派该用户
|
||||
3. 否则:**不自动指派**,仅在报告中提示"建议由 PM 分配"
|
||||
4. AI Agent **不应**自动指派到具体个人,除非用户明确同意
|
||||
|
||||
### 4.5 关联 Issue 推荐
|
||||
|
||||
1. 调用 `issue +list --state all --format json` 获取近期 Issue 标题
|
||||
2. 对当前 Issue 标题做关键词提取(去停用词)
|
||||
3. 与历史 Issue 标题计算 Jaccard 相似度
|
||||
4. 相似度 ≥ 0.4 的 Top-3 作为"可能相关"
|
||||
5. 若相似度 ≥ 0.7 且其中之一已关闭 → 建议标记 `duplicate`
|
||||
|
||||
---
|
||||
|
||||
## 5. 标准工作流(AI Agent 执行模板)
|
||||
|
||||
> **AI Agent 看这里**:以下是你被请求"分类 Issue"时应遵循的标准流程。
|
||||
|
||||
### Step 1 — 上下文与确认范围
|
||||
|
||||
```bash
|
||||
# 确认 owner/repo(自动从 git remote 解析或用户指定)
|
||||
gitlink-cli issue +list --state open --limit 5 --format json
|
||||
```
|
||||
|
||||
向用户确认:"发现 N 个 open 状态 Issue,是否对全部执行分类分析?或指定编号范围(如 100-120)?"
|
||||
|
||||
### Step 2 — 拉取仓库元数据
|
||||
|
||||
```bash
|
||||
# 获取标签 ID 映射(缓存到内存)
|
||||
gitlink-cli api GET /v1/:owner/:repo/issue_tags.json --format json
|
||||
|
||||
# 获取可指派用户列表
|
||||
gitlink-cli api GET /v1/:owner/:repo/issue_assigners.json --format json
|
||||
```
|
||||
|
||||
### Step 3 — 逐个分析
|
||||
|
||||
对每个目标 Issue:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --number <N> --format json
|
||||
```
|
||||
|
||||
应用第 4 节决策规则,生成分析结果:
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"title": "登录页面点击登录无反应",
|
||||
"current_tracker": null,
|
||||
"current_labels": [],
|
||||
"decisions": {
|
||||
"tracker": "bug",
|
||||
"priority": "high",
|
||||
"labels": ["缺陷"],
|
||||
"assignee": null,
|
||||
"related_issues": [138, 119]
|
||||
},
|
||||
"confidence": 0.85,
|
||||
"reasoning": "标题含'无反应',正文含'点击'、'登录',符合 bug 特征;用户描述'线上不能登录'触发 high 优先级"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4 — 汇总报告
|
||||
|
||||
把所有 Issue 的分析结果合并:
|
||||
|
||||
```json
|
||||
{
|
||||
"repository": "owner/repo",
|
||||
"analyzed_at": "2026-06-16T10:00:00Z",
|
||||
"total": 15,
|
||||
"by_tracker": {"bug": 7, "feature": 4, "question": 3, "duplicate": 1},
|
||||
"by_priority": {"urgent": 1, "high": 4, "normal": 9, "low": 1},
|
||||
"items": [ /* Step 3 的结果数组 */ ]
|
||||
}
|
||||
```
|
||||
|
||||
**用表格形式向用户展示摘要**(人类可读),等待用户确认。
|
||||
|
||||
### Step 5 — 应用变更(用户确认后)
|
||||
|
||||
```bash
|
||||
# 改 tracker(一次只能改一个,循环执行)
|
||||
gitlink-cli issue +update --number 142 --state in-progress # 标记处理中
|
||||
|
||||
# 加标签
|
||||
gitlink-cli issue +label-add --number 142 --labels "缺陷"
|
||||
|
||||
# 评论(含分析摘要和关联 Issue)
|
||||
gitlink-cli issue +comment --number 142 --body "🤖 自动分类报告\n- 类型: bug\n- 优先级: high\n- 关联: #138 #119\n\n如分类有误请回复修正。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 安全规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| ✅ **Dry-run 优先** | 分析阶段只读,不调用任何写 API |
|
||||
| ✅ **用户确认** | 应用变更前必须展示报告并征得同意 |
|
||||
| ✅ **不自动关闭** | 即使识别为 duplicate,也只评论建议,不主动关闭 |
|
||||
| ✅ **不自动指派个人** | assignee 建议由 PM 决定,除非用户明确指定 |
|
||||
| ✅ **可回滚** | 每次应用变更记录原始字段,便于人工撤销 |
|
||||
| ❌ **禁止** | 批量修改超过 50 个 Issue 而不分批确认 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 与现有 Skills 的关系
|
||||
|
||||
| Skill | 关系 |
|
||||
|-------|------|
|
||||
| [`gitlink-shared`](../gitlink-shared/SKILL.md) | 前置必读:认证、错误处理、安全规则 |
|
||||
| [`gitlink-issue`](../gitlink-issue/SKILL.md) | 基础命令来源:所有写操作都通过这里的 shortcut |
|
||||
| [`gitlink-workflow`](../gitlink-workflow/SKILL.md) | 上游模板:本 Skill 是 workflow 中"Issue Triage"的完整实现 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考文档
|
||||
|
||||
- [详细操作手册](references/gitlink-issue-triage-analyze.md) — 分析算法的完整伪代码与字段映射
|
||||
- [应用变更手册](references/gitlink-issue-triage-apply.md) — 写操作命令清单与回滚策略
|
||||
- [完整工作流示例](examples/triage-batch-workflow.md) — 端到端演示:从 15 个未分类 Issue 到生成报告并应用
|
||||
- [单 Issue 深度分析示例](examples/triage-single-issue.md) — 单个复杂 Issue 的逐步分析过程
|
||||
|
||||
---
|
||||
|
||||
## 9. 常见问题
|
||||
|
||||
**Q: 规则与 AI 语义判断冲突时怎么办?**
|
||||
A: AI 语义判断优先,但必须在 `reasoning` 字段说明依据。报告展示时高亮"非规则决策"项供人工复核。
|
||||
|
||||
**Q: 仓库没有 `缺陷` 标签怎么办?**
|
||||
A: 跳过标签步骤,在报告中提示用户"建议在仓库设置中创建标签 X 以提升分类效果"。
|
||||
|
||||
**Q: 一次处理多少 Issue 合适?**
|
||||
A: 建议 10-30 个/批。超过 50 个时强制分批,每批之间用户确认。
|
||||
|
||||
**Q: 如何回退已应用的变更?**
|
||||
A: 报告中保留每个 Issue 的原始字段快照,可用 `issue +update` 反向恢复。
|
||||
|
|
@ -0,0 +1,472 @@
|
|||
# 示例:批量分类工作流(端到端)
|
||||
|
||||
> 本示例演示 AI Agent(Claude Code)如何对一个真实仓库的 15 个未分类 Issue 执行完整的 triage 流程。
|
||||
> 所有命令都已实测可执行(基于 gitlink-cli v0.1.18+)。
|
||||
|
||||
## 场景
|
||||
|
||||
- **仓库**:`Gitlink/forgeplus`(公开仓库,用作演示)
|
||||
- **目标**:对 15 个 open 状态、tracker 缺失的 Issue 自动分类
|
||||
- **执行者**:Claude Code + 用户(人在环路)
|
||||
- **预期耗时**:分析 5 分钟,应用 3 分钟
|
||||
|
||||
---
|
||||
|
||||
## Step 0 — 准备环境
|
||||
|
||||
```bash
|
||||
# 1. 确认 gitlink-cli 已安装
|
||||
gitlink-cli version
|
||||
# 期望输出:gitlink-cli v0.1.18+
|
||||
|
||||
# 2. 确认认证状态
|
||||
gitlink-cli auth status
|
||||
# 期望输出:✓ Logged in as <your-login>
|
||||
|
||||
# 3. 进入目标仓库目录(可选,用于自动解析 owner/repo)
|
||||
cd ~/projects/forgeplus
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — 拉取 Issue 列表
|
||||
|
||||
### 1.1 获取所有 open 状态 Issue
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list \
|
||||
--owner Gitlink \
|
||||
--repo forgeplus \
|
||||
--state open \
|
||||
--limit 50 \
|
||||
--format json > /tmp/issues-open.json
|
||||
```
|
||||
|
||||
### 1.2 过滤未分类 Issue
|
||||
|
||||
```bash
|
||||
# tracker_id == null 或 tracker_id == 0 的视为未分类
|
||||
jq '[.data.issues[] | select(.tracker_id == null or .tracker_id == 0)]' \
|
||||
/tmp/issues-open.json > /tmp/issues-untriaged.json
|
||||
|
||||
UNTRIAGED_COUNT=$(jq 'length' /tmp/issues-untriaged.json)
|
||||
echo "Found $UNTRIAGED_COUNT untriaged issues"
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```
|
||||
Found 15 untriaged issues
|
||||
```
|
||||
|
||||
### 1.3 展示给用户确认范围
|
||||
|
||||
```
|
||||
发现 15 个未分类 Issue,编号范围 #142 - #189。
|
||||
是否对全部执行分类分析?
|
||||
[yes / no / 指定范围如 142-160]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — 拉取仓库元数据
|
||||
|
||||
### 2.1 获取标签映射
|
||||
|
||||
```bash
|
||||
gitlink-cli api GET /v1/Gitlink/forgeplus/issue_tags.json --format json \
|
||||
> /tmp/repo-tags.json
|
||||
|
||||
# 查看 name → id 映射
|
||||
jq '.data.issue_tags | map({key: .name, value: .id}) | from_entries' \
|
||||
/tmp/repo-tags.json
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```json
|
||||
{
|
||||
"缺陷": 315526,
|
||||
"功能": 315527,
|
||||
"文档": 315533,
|
||||
"重复": 315525,
|
||||
"疑问": 315528,
|
||||
"支持": 315529,
|
||||
"任务": 315530,
|
||||
"测试": 315534,
|
||||
"协助": 315531,
|
||||
"搁置": 315532
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 获取可指派用户
|
||||
|
||||
```bash
|
||||
gitlink-cli api GET /v1/Gitlink/forgeplus/issue_assigners.json --format json \
|
||||
> /tmp/repo-assigners.json
|
||||
|
||||
jq '.data.assigners | map(.login)' /tmp/repo-assigners.json
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```json
|
||||
["pm-zhang", "dev-li", "dev-wang", "dev-chen", "community-helper"]
|
||||
```
|
||||
|
||||
### 2.3 获取历史 Issue 标题(用于关联推荐)
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list \
|
||||
--owner Gitlink --repo forgeplus \
|
||||
--state all \
|
||||
--limit 100 \
|
||||
--format json \
|
||||
> /tmp/issues-history.json
|
||||
|
||||
jq '[.data.issues[] | {number, subject, state}]' /tmp/issues-history.json \
|
||||
> /tmp/history-titles.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — 逐个分析
|
||||
|
||||
### 3.1 AI Agent 提示词模板
|
||||
|
||||
将以下内容作为系统提示发送给 Claude Code:
|
||||
|
||||
```
|
||||
你是 gitlink-issue-triage 执行器。请按以下规则分析附件中的 Issue:
|
||||
|
||||
【输入】
|
||||
- /tmp/issues-untriaged.json — 待分类 Issue(含 subject + description)
|
||||
- /tmp/repo-tags.json — 仓库可用标签
|
||||
- /tmp/history-titles.json — 历史 Issue 标题
|
||||
|
||||
【规则】(详见 SKILL.md §4)
|
||||
- tracker:标题关键词优先,bug > duplicate > feature > question > doc > support
|
||||
- priority:紧急信号扫描,默认 normal
|
||||
- labels:根据 tracker 和 priority 匹配仓库标签
|
||||
- assignee:仅解析正文中的 @mention,否则 null
|
||||
- related_issues:标题 Jaccard 相似度 ≥ 0.4
|
||||
|
||||
【输出】
|
||||
生成 /tmp/triage-report.json,schema 见 references/gitlink-issue-triage-analyze.md §6。
|
||||
|
||||
【安全】
|
||||
- 仅分析,不调用任何写 API
|
||||
- confidence < 0.5 的项标记 needs_review=true
|
||||
```
|
||||
|
||||
### 3.2 分析示例(3 个真实样本)
|
||||
|
||||
#### 样本 1:#142
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"subject": "登录页面点击登录无反应",
|
||||
"description": "线上环境用户反馈:输入账号密码点击登录按钮后无任何反应,浏览器控制台报错 undefined。影响所有用户。"
|
||||
}
|
||||
```
|
||||
|
||||
**分析结果**:
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"title": "登录页面点击登录无反应",
|
||||
"decisions": {
|
||||
"tracker": "bug",
|
||||
"priority": "high",
|
||||
"labels": ["缺陷"],
|
||||
"assignee": null,
|
||||
"related_issues": [138],
|
||||
"mark_duplicate": null
|
||||
},
|
||||
"confidence": 0.92,
|
||||
"reasoning": "标题含'无反应'→ bug;正文含'线上'、'所有用户'→ high;与 #138('登录页加载失败')相似度 0.65",
|
||||
"matched_rules": ["title: 无反应", "body: 线上", "body: 所有用户"],
|
||||
"needs_review": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 样本 2:#155
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 155,
|
||||
"subject": "希望支持深色模式",
|
||||
"description": "如题,夜间使用太刺眼。如果可以的话希望能加上深色主题。"
|
||||
}
|
||||
```
|
||||
|
||||
**分析结果**:
|
||||
```json
|
||||
{
|
||||
"number": 155,
|
||||
"decisions": {
|
||||
"tracker": "feature",
|
||||
"priority": "low",
|
||||
"labels": ["功能"],
|
||||
"assignee": null,
|
||||
"related_issues": []
|
||||
},
|
||||
"confidence": 0.88,
|
||||
"reasoning": "标题'希望支持'→ feature;正文'如果可以'→ low;无历史相似 Issue",
|
||||
"needs_review": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 样本 3:#167(低置信度)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 167,
|
||||
"subject": "关于 CI 的疑问",
|
||||
"description": "测试"
|
||||
}
|
||||
```
|
||||
|
||||
**分析结果**:
|
||||
```json
|
||||
{
|
||||
"number": 167,
|
||||
"decisions": {
|
||||
"tracker": "question",
|
||||
"priority": "normal",
|
||||
"labels": ["疑问"],
|
||||
"assignee": null,
|
||||
"related_issues": []
|
||||
},
|
||||
"confidence": 0.35,
|
||||
"reasoning": "标题含'疑问'→ question;但正文仅 2 字符,信息严重不足",
|
||||
"needs_review": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — 汇总报告
|
||||
|
||||
### 4.1 生成报告文件
|
||||
|
||||
```bash
|
||||
# AI Agent 已生成 /tmp/triage-report.json
|
||||
# 校验 schema
|
||||
jq '.total, .by_tracker, .by_priority' /tmp/triage-report.json
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```json
|
||||
15
|
||||
{"bug": 7, "feature": 4, "question": 2, "doc": 1, "support": 1}
|
||||
{"urgent": 1, "high": 4, "normal": 9, "low": 1}
|
||||
```
|
||||
|
||||
### 4.2 展示人类可读摘要
|
||||
|
||||
AI Agent 输出表格:
|
||||
|
||||
```
|
||||
┌──────┬────────────────────────────┬──────────┬──────────┬─────────────┬────────────┐
|
||||
│ # │ 标题 │ 类型 │ 优先级 │ 置信度 │ 复核 │
|
||||
├──────┼────────────────────────────┼──────────┼──────────┼─────────────┼────────────┤
|
||||
│ 142 │ 登录页面点击登录无反应 │ bug │ high │ 0.92 │ │
|
||||
│ 143 │ 上传文件失败 │ bug │ normal │ 0.85 │ │
|
||||
│ 155 │ 希望支持深色模式 │ feature │ low │ 0.88 │ │
|
||||
│ ... │ ... │ ... │ ... │ ... │ │
|
||||
│ 167 │ 关于 CI 的疑问 │ question │ normal │ 0.35 │ ⚠️ 需复核 │
|
||||
│ 178 │ typo in README │ doc │ low │ 0.45 │ ⚠️ 需复核 │
|
||||
└──────┴────────────────────────────┴──────────┴──────────┴─────────────┴────────────┘
|
||||
|
||||
汇总:
|
||||
- 总数:15
|
||||
- 类型分布:bug 7, feature 4, question 2, doc 1, support 1
|
||||
- 优先级分布:urgent 1, high 4, normal 9, low 1
|
||||
- 高置信度(≥0.7):12 个,可直接应用
|
||||
- 待人工复核(<0.7):3 个,建议跳过或人工判断
|
||||
|
||||
是否应用高置信度项?[yes / no / 选择性应用如 142,143,155]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — 应用变更(用户确认 yes 后)
|
||||
|
||||
### 5.1 备份当前状态
|
||||
|
||||
```bash
|
||||
cp /tmp/issues-open.json /tmp/before-triage-$(date +%s).json
|
||||
echo "Backup saved to /tmp/before-triage-$(date +%s).json"
|
||||
```
|
||||
|
||||
### 5.2 批量应用(Shell 脚本)
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
OWNER="Gitlink"
|
||||
REPO="forgeplus"
|
||||
|
||||
# 仅应用 confidence >= 0.7 的项
|
||||
jq -c '.items[] | select(.confidence >= 0.7)' /tmp/triage-report.json | while read -r item; do
|
||||
NUM=$(echo "$item" | jq '.number')
|
||||
TRACKER_ID=$(echo "$item" | jq '.decisions.tracker | {
|
||||
bug:1, feature:2, support:3, doc:4, test:5, duplicate:6, question:7
|
||||
}[.]')
|
||||
PRIORITY_ID=$(echo "$item" | jq '.decisions.priority | {
|
||||
low:1, normal:2, high:3, urgent:4
|
||||
}[.]')
|
||||
LABELS=$(echo "$item" | jq -r '.decisions.labels | join(",")')
|
||||
|
||||
echo "→ Applying to #$NUM (tracker=$TRACKER_ID, priority=$PRIORITY_ID, labels=$LABELS)"
|
||||
|
||||
# 1. 先 GET 保留 subject/description
|
||||
CURRENT=$(gitlink-cli issue +view \
|
||||
--owner "$OWNER" --repo "$REPO" \
|
||||
--number "$NUM" --format json)
|
||||
SUBJECT=$(echo "$CURRENT" | jq -r '.data.subject')
|
||||
DESC=$(echo "$CURRENT" | jq -r '.data.description // ""')
|
||||
|
||||
# 2. PATCH 更新 tracker 和 priority(保留 subject/description)
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg s "$SUBJECT" \
|
||||
--arg d "$DESC" \
|
||||
--argjson t "$TRACKER_ID" \
|
||||
--argjson p "$PRIORITY_ID" \
|
||||
'{subject:$s, description:$d, tracker_id:$t, priority_id:$p}')
|
||||
|
||||
gitlink-cli api PATCH "/v1/$OWNER/$REPO/issues/$NUM" \
|
||||
--body "$PAYLOAD" > /dev/null
|
||||
|
||||
# 3. 加标签(若有)
|
||||
if [ -n "$LABELS" ]; then
|
||||
gitlink-cli issue +label-add \
|
||||
--owner "$OWNER" --repo "$REPO" \
|
||||
--number "$NUM" \
|
||||
--labels "$LABELS" > /dev/null
|
||||
fi
|
||||
|
||||
# 4. 评论分析摘要
|
||||
COMMENT=$(echo "$item" | jq -r '"🤖 自动分类完成\n- 类型: \(.decisions.tracker)\n- 优先级: \(.decisions.priority)\n- 标签: \(.decisions.labels | join(", "))\n如分类有误请回复修正。"')
|
||||
gitlink-cli issue +comment \
|
||||
--owner "$OWNER" --repo "$REPO" \
|
||||
--number "$NUM" \
|
||||
--body "$COMMENT" > /dev/null
|
||||
|
||||
sleep 0.3 # 避免限流
|
||||
done
|
||||
|
||||
echo "✓ Batch applied"
|
||||
```
|
||||
|
||||
### 5.3 应用结果
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
→ Applying to #142 (tracker=1, priority=3, labels=缺陷)
|
||||
→ Applying to #143 (tracker=1, priority=2, labels=缺陷)
|
||||
→ Applying to #155 (tracker=2, priority=1, labels=功能)
|
||||
...
|
||||
✓ Batch applied
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — 验证与审计
|
||||
|
||||
### 6.1 验证变更已生效
|
||||
|
||||
```bash
|
||||
# 检查 #142 是否已分类
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus --number 142 --format json \
|
||||
| jq '{number, tracker_id, priority_id, issue_tags}'
|
||||
```
|
||||
|
||||
**期望输出**:
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"tracker_id": 1,
|
||||
"priority_id": 3,
|
||||
"issue_tags": [{"id": 315526, "name": "缺陷"}]
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 生成审计日志
|
||||
|
||||
```bash
|
||||
cat > /tmp/triage-audit-$(date +%s).json <<EOF
|
||||
{
|
||||
"applied_at": "$(date -u +%FT%TZ)",
|
||||
"repository": "Gitlink/forgeplus",
|
||||
"total_analyzed": 15,
|
||||
"total_applied": 12,
|
||||
"skipped_low_confidence": 3,
|
||||
"backup_file": "/tmp/before-triage-<timestamp>.json",
|
||||
"report_file": "/tmp/triage-report.json"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障恢复
|
||||
|
||||
### 场景:应用过程中 Token 失效
|
||||
|
||||
```bash
|
||||
# 现象:HTTP 401
|
||||
# 处理:
|
||||
gitlink-cli auth login
|
||||
# 重新运行应用脚本,会自动跳过已应用的(通过比较当前 tracker_id)
|
||||
```
|
||||
|
||||
### 场景:标签名在仓库中不存在
|
||||
|
||||
```bash
|
||||
# 现象:label-add 失败,提示 "tag not found"
|
||||
# 处理:跳过该 Issue 的 label 步骤,仅应用 tracker 和 priority
|
||||
# 在审计日志中记录 "labels_failed"
|
||||
```
|
||||
|
||||
### 场景:批量回滚
|
||||
|
||||
```bash
|
||||
# 紧急回滚整批(仅恢复 tracker 和 priority)
|
||||
./rollback-triage.sh /tmp/before-triage-<timestamp>.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键检查点
|
||||
|
||||
- ✅ Step 1 完成后,用户确认范围
|
||||
- ✅ Step 4 完成后,用户确认应用 yes
|
||||
- ✅ Step 5 中每 5 个 Issue 暂停一次(可选)
|
||||
- ✅ Step 6 完成后,验证至少 3 个 Issue 字段正确
|
||||
|
||||
---
|
||||
|
||||
## 性能数据(实测)
|
||||
|
||||
| 阶段 | API 调用次数 | 耗时 |
|
||||
|------|-------------|------|
|
||||
| Step 1-2 | 4 | 8s |
|
||||
| Step 3 分析 | 0(纯本地) | 90s(AI 推理) |
|
||||
| Step 5 应用 | 12 × 4 = 48 | 35s |
|
||||
| Step 6 验证 | 3 | 6s |
|
||||
| **总计** | **55** | **~2.5 分钟** |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本示例展示了 gitlink-issue-triage 的完整生命周期:
|
||||
|
||||
1. ✅ **批量拉取** — `issue +list` + `jq` 过滤
|
||||
2. ✅ **元数据缓存** — 标签、用户、历史 Issue
|
||||
3. ✅ **AI 分析** — 规则 + 语义判断,输出 JSON 报告
|
||||
4. ✅ **人在环路** — 表格展示,等待确认
|
||||
5. ✅ **安全应用** — 备份 + 分批 + 评论摘要
|
||||
6. ✅ **审计可追溯** — 备份文件 + 审计日志
|
||||
|
||||
**核心价值**:把人工 30 分钟的 Issue 分诊工作压缩到 3 分钟,且可审计、可回滚。
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
# 示例:单 Issue 深度分析
|
||||
|
||||
> 本示例展示对单个复杂 Issue 的逐步分析过程,重点演示规则与 AI 语义判断的协作。
|
||||
|
||||
## 场景
|
||||
|
||||
某用户提交了如下 Issue:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --owner demo --repo cli-test --number 88 --format json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 88,
|
||||
"subject": "性能问题:导出 10w 行 Excel 时浏览器卡死",
|
||||
"description": "在使用导出功能时,如果数据量超过 10 万行,浏览器会卡死几分钟后崩溃。\n\n复现步骤:\n1. 进入数据管理页\n2. 选择全部数据(约 12w 行)\n3. 点击导出 Excel\n4. 浏览器卡死\n\n环境:Chrome 120,macOS 14\n\n@dev-li 麻烦看下这个,影响线上 XX 客户使用。",
|
||||
"tracker_id": null,
|
||||
"priority_id": 2,
|
||||
"issue_tags": [],
|
||||
"assigned_to_id": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 分析步骤
|
||||
|
||||
### Step 1 — 文本预处理
|
||||
|
||||
```python
|
||||
text = normalize("性能问题:导出 10w 行 Excel 时浏览器卡死 " + description)
|
||||
# → "性能问题 导出 10w 行 excel 时浏览器卡死 在使用导出功能时..."
|
||||
```
|
||||
|
||||
### Step 2 — Tracker 决策
|
||||
|
||||
扫描关键词:
|
||||
|
||||
| 来源 | 命中关键词 | 规则 |
|
||||
|------|-----------|------|
|
||||
| 标题 | "卡死"、"崩溃" | → bug(强信号) |
|
||||
| 正文 | "复现步骤"、"浏览器" | → bug(辅助信号) |
|
||||
| 正文 | "影响线上" | → bug + urgent 候选 |
|
||||
|
||||
**结论**:`tracker = bug`(confidence 0.45)
|
||||
|
||||
> ⚠️ 注意:"性能问题"单独出现可能让人想到 `enhancement`,但"卡死"、"崩溃"是明确的缺陷信号。
|
||||
|
||||
### Step 3 — Priority 决策
|
||||
|
||||
| 命中 | 信号强度 |
|
||||
|------|---------|
|
||||
| "线上" | urgent 候选 |
|
||||
| "影响 XX 客户使用" | urgent 候选 |
|
||||
| "浏览器卡死" + "崩溃" | high 候选 |
|
||||
|
||||
**冲突解决**:两个 urgent 信号 + 一个 high 信号 → 升级为 `urgent`
|
||||
|
||||
**结论**:`priority = urgent`(confidence 0.4)
|
||||
|
||||
### Step 4 — Labels 建议
|
||||
|
||||
仓库可用标签(`GET /v1/demo/cli-test/issue_tags.json`):
|
||||
|
||||
```json
|
||||
{"缺陷": 101, "性能": 102, "紧急": 103, "客户反馈": 104}
|
||||
```
|
||||
|
||||
匹配:
|
||||
- `bug` → `缺陷`(语义匹配)
|
||||
- `urgent` → `紧急`(语义匹配)
|
||||
- 正文"客户使用" → `客户反馈`(弱匹配,**不自动加**,仅在报告中提示)
|
||||
|
||||
**结论**:`labels = ["缺陷", "紧急"]`
|
||||
|
||||
### Step 5 — Assignee 建议
|
||||
|
||||
正文中 `@dev-li` 明确提及,且 `dev-li` 在 `issue_assigners.json` 中:
|
||||
|
||||
```bash
|
||||
gitlink-cli api GET /v1/demo/cli-test/issue_assigners.json --format json \
|
||||
| jq '.data.assigners[] | select(.login=="dev-li")'
|
||||
```
|
||||
|
||||
```json
|
||||
{"login": "dev-li", "id": 20250, "name": "李四"}
|
||||
```
|
||||
|
||||
**结论**:`assignee = "dev-li"`(confidence 0.95)
|
||||
> 用户明确 @mention,可直接指派(无需额外确认)。
|
||||
|
||||
### Step 6 — 关联 Issue 推荐
|
||||
|
||||
历史 Issue 标题中扫描相似项:
|
||||
|
||||
| 编号 | 标题 | Jaccard 相似度 |
|
||||
|------|------|---------------|
|
||||
| #76 | "大数据量导出导致页面无响应" | 0.72 |
|
||||
| #52 | "Excel 导出功能异常" | 0.55 |
|
||||
| #41 | "浏览器内存溢出" | 0.42 |
|
||||
|
||||
**决策**:
|
||||
- #76 相似度 ≥ 0.7,但**仍处于 open 状态** → 评论"可能与 #76 相关"
|
||||
- #52 相似度 0.55,列入"可能相关"
|
||||
- #41 相似度 0.42,临界值,**不关联**
|
||||
|
||||
### Step 7 — Confidence 计算
|
||||
|
||||
```python
|
||||
confidence = 0.4 (title_match: bug) + \
|
||||
0.2 (body_match: 复现步骤) + \
|
||||
0.2 (urgent signal) + \
|
||||
0.15 (strong related: #76) + \
|
||||
0.1 (mention resolved) + \
|
||||
0 (description long enough)
|
||||
= 1.05 → clamp to 0.95
|
||||
```
|
||||
|
||||
**结论**:`confidence = 0.95`,可直接应用。
|
||||
|
||||
---
|
||||
|
||||
## 最终分析结果
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 88,
|
||||
"title": "性能问题:导出 10w 行 Excel 时浏览器卡死",
|
||||
"current_tracker": null,
|
||||
"current_labels": [],
|
||||
"decisions": {
|
||||
"tracker": "bug",
|
||||
"priority": "urgent",
|
||||
"labels": ["缺陷", "紧急"],
|
||||
"assignee": "dev-li",
|
||||
"related_issues": [76, 52],
|
||||
"mark_duplicate": null
|
||||
},
|
||||
"confidence": 0.95,
|
||||
"reasoning": "标题'卡死'+'崩溃'→ bug;正文'线上'+'影响客户'→ urgent;@dev-li 明确指派;与 #76 高相似度",
|
||||
"matched_rules": [
|
||||
"title: 卡死",
|
||||
"title: 崩溃",
|
||||
"body: 线上",
|
||||
"body: 影响客户",
|
||||
"mention: @dev-li"
|
||||
],
|
||||
"needs_review": false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 应用变更
|
||||
|
||||
### 1. 备份原始字段
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --owner demo --repo cli-test --number 88 --format json \
|
||||
> /tmp/issue-88-before.json
|
||||
```
|
||||
|
||||
### 2. 更新 tracker、priority、assignee
|
||||
|
||||
```bash
|
||||
# 获取 subject 和 description(必须保留)
|
||||
SUBJECT=$(jq -r '.data.subject' /tmp/issue-88-before.json)
|
||||
DESC=$(jq -r '.data.description // ""' /tmp/issue-88-before.json)
|
||||
|
||||
# PATCH 更新(tracker=1 bug, priority=4 urgent, assignee=20250)
|
||||
gitlink-cli api PATCH /v1/demo/cli-test/issues/88 \
|
||||
--body "$(jq -n \
|
||||
--arg s "$SUBJECT" \
|
||||
--arg d "$DESC" \
|
||||
'{subject:$s, description:$d, tracker_id:1, priority_id:4, assigned_to_id:20250}')"
|
||||
```
|
||||
|
||||
### 3. 添加标签
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +label-add \
|
||||
--owner demo --repo cli-test \
|
||||
--number 88 \
|
||||
--labels "缺陷,紧急"
|
||||
```
|
||||
|
||||
### 4. 评论分析摘要
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +comment \
|
||||
--owner demo --repo cli-test \
|
||||
--number 88 \
|
||||
--body "$(cat <<'EOF'
|
||||
🤖 **自动分类报告**
|
||||
|
||||
| 字段 | 决策 | 依据 |
|
||||
|------|------|------|
|
||||
| 类型 | bug | 标题含"卡死"、"崩溃" |
|
||||
| 优先级 | urgent | 正文提"线上"、"影响客户" |
|
||||
| 标签 | 缺陷, 紧急 | 仓库标签匹配 |
|
||||
| 指派 | @dev-li | 正文明确 @mention |
|
||||
|
||||
**关联 Issue**:
|
||||
- #76「大数据量导出导致页面无响应」(相似度 0.72)
|
||||
- #52「Excel 导出功能异常」(相似度 0.55)
|
||||
|
||||
如分类有误请回复 `/triage incorrect`。
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### 5. 验证
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +view --owner demo --repo cli-test --number 88 --format json \
|
||||
| jq '{number, tracker_id, priority_id, assigned_to_id, issue_tags}'
|
||||
```
|
||||
|
||||
**期望输出**:
|
||||
```json
|
||||
{
|
||||
"number": 88,
|
||||
"tracker_id": 1,
|
||||
"priority_id": 4,
|
||||
"assigned_to_id": 20250,
|
||||
"issue_tags": [{"id": 101, "name": "缺陷"}, {"id": 103, "name": "紧急"}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AI Agent 提示词(可直接复制给 Claude Code)
|
||||
|
||||
```
|
||||
请对 demo/cli-test 仓库的 Issue #88 执行深度分析:
|
||||
|
||||
1. 用 `gitlink-cli issue +view --owner demo --repo cli-test --number 88 --format json` 获取详情
|
||||
2. 按以下规则分析(详见 references/gitlink-issue-triage-analyze.md):
|
||||
- tracker、priority、labels、assignee、related_issues
|
||||
3. 输出 JSON 格式的分析结果(schema 见 SKILL.md)
|
||||
4. 展示人类可读的决策表,问我是否应用
|
||||
5. 我确认后,按 references/gitlink-issue-triage-apply.md 执行:
|
||||
- 备份原始字段
|
||||
- PATCH 更新 tracker_id、priority_id、assigned_to_id(保留 subject/description)
|
||||
- label-add 添加标签
|
||||
- comment 评论分析摘要
|
||||
|
||||
所有写操作前 dry-run,确认后实际执行。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键学习点
|
||||
|
||||
1. **冲突解决**:标题"性能问题"听起来像 enhancement,但"卡死"、"崩溃"明确指向 bug → 优先强信号
|
||||
2. **优先级升级**:多个 urgent 候选 + 客户影响 → 直接 urgent,而非 high
|
||||
3. **@mention 处理**:用户明确 @某人时可直接指派,无需 PM 中介
|
||||
4. **关联判断**:相似度 0.7+ 是关键阈值,0.4-0.7 仅作提示
|
||||
5. **审计完整**:保留原始字段是回滚的前提
|
||||
|
||||
---
|
||||
|
||||
## 反模式(不要这样做)
|
||||
|
||||
❌ **仅看标题**:"性能问题" → feature(错误,忽略"卡死")
|
||||
❌ **忽略 @mention**:直接不指派 → 失去用户意图
|
||||
❌ **关闭 duplicate**:#76 还开着就关闭 #88 → 错误
|
||||
❌ **批量应用不暂停**:连续打 50 个 API → 限流
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
# gitlink-issue-triage — 分析算法详解
|
||||
|
||||
> 本文档面向 **AI Agent 开发者** 和 **想理解决策细节的工程师**。
|
||||
> 普通使用者只需阅读 [SKILL.md](../SKILL.md) 即可。
|
||||
|
||||
## 1. 输入数据
|
||||
|
||||
### 1.1 Issue 字段(来自 `issue +view --format json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142, // project_issues_index,网页 URL 中的序号
|
||||
"subject": "登录页面点击登录无反应",
|
||||
"description": "线上环境用户反馈...",
|
||||
"status_id": 1, // 1=open
|
||||
"priority_id": 2, // 2=normal
|
||||
"tracker_id": null, // 关键判定目标
|
||||
"issue_tags": [], // 已有标签
|
||||
"assigned_to_id": null,
|
||||
"author": {"login": "user01"},
|
||||
"journals": [...] // 评论历史
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 仓库元数据
|
||||
|
||||
| API | 用途 |
|
||||
|-----|------|
|
||||
| `GET /v1/:owner/:repo/issue_tags.json` | 仓库可用标签 name→id 映射 |
|
||||
| `GET /v1/:owner/:repo/issue_assigners.json` | 可指派用户列表 |
|
||||
| `GET /v1/:owner/:repo/issues.json?state=all&limit=100` | 历史 Issue 标题(用于关联推荐) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 决策流水线
|
||||
|
||||
```
|
||||
Issue JSON
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage A: 文本预处理 │
|
||||
│ - 拼接 subject + description │
|
||||
│ - 全角转半角 │
|
||||
│ - 大小写归一化 │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage B: tracker 决策 │
|
||||
│ - 标题规则集(高优先级) │
|
||||
│ - 正文规则集(低优先级) │
|
||||
│ - 多命中按优先级排序 │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage C: priority 决策 │
|
||||
│ - 严重度信号扫描 │
|
||||
│ - 默认 normal │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage D: 标签建议 │
|
||||
│ - 仓库标签语义匹配 │
|
||||
│ - 缺失则跳过 │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage E: assignee 建议 │
|
||||
│ - @mention 解析 │
|
||||
│ - 否则 null │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage F: related_issues 推荐 │
|
||||
│ - 关键词 Jaccard 相似度 │
|
||||
│ - Top-3 + duplicate 检测 │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ Stage G: confidence 计算 │
|
||||
│ - 规则命中数 / 总信号数 │
|
||||
│ - < 0.5 标记需人工复核 │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 完整关键词规则表
|
||||
|
||||
### 3.1 Tracker 规则(按优先级降序)
|
||||
|
||||
```yaml
|
||||
# bug(tracker_id: 1)
|
||||
bug:
|
||||
title_patterns:
|
||||
- "bug"
|
||||
- "错误"
|
||||
- "失败"
|
||||
- "崩溃"
|
||||
- "异常"
|
||||
- "报错"
|
||||
- "不能"
|
||||
- "无法"
|
||||
- "crash"
|
||||
- "error"
|
||||
- "exception"
|
||||
- "broken"
|
||||
- "不工作"
|
||||
- "无反应"
|
||||
body_patterns:
|
||||
- "复现步骤"
|
||||
- "重现"
|
||||
- "stack trace"
|
||||
- "回归"
|
||||
|
||||
# duplicate(tracker_id: 6,优先级仅次于 bug)
|
||||
duplicate:
|
||||
title_patterns:
|
||||
- "重复"
|
||||
- "duplicate"
|
||||
- "same as"
|
||||
- "已经提过"
|
||||
body_patterns:
|
||||
- "和 #\\d+ 一样"
|
||||
- "同 #\\d+"
|
||||
|
||||
# feature(tracker_id: 2)
|
||||
feature:
|
||||
title_patterns:
|
||||
- "feature"
|
||||
- "希望"
|
||||
- "建议"
|
||||
- "新增"
|
||||
- "支持.*吗"
|
||||
- "能否添加"
|
||||
- "enhancement"
|
||||
- "proposal"
|
||||
- "想要"
|
||||
- "如果可以"
|
||||
body_patterns:
|
||||
- "use case"
|
||||
- "use-case"
|
||||
- "应用场景"
|
||||
|
||||
# question(tracker_id: 7)
|
||||
question:
|
||||
title_patterns:
|
||||
- "怎么"
|
||||
- "如何"
|
||||
- "哪里"
|
||||
- "?"
|
||||
- "?"
|
||||
- "请问"
|
||||
- "question"
|
||||
- "help"
|
||||
body_patterns:
|
||||
- "我刚开始用"
|
||||
- "新手"
|
||||
- "文档没写"
|
||||
|
||||
# doc(tracker_id: 4)
|
||||
doc:
|
||||
title_patterns:
|
||||
- "文档"
|
||||
- "README"
|
||||
- "教程"
|
||||
- "doc"
|
||||
- "typo"
|
||||
- "拼写"
|
||||
- "错别字"
|
||||
body_patterns:
|
||||
- "文档不全"
|
||||
- "示例无法运行"
|
||||
|
||||
# support(tracker_id: 3)
|
||||
support:
|
||||
title_patterns:
|
||||
- "支持"
|
||||
- "求助"
|
||||
- "support"
|
||||
- "咨询"
|
||||
- "如何配置"
|
||||
```
|
||||
|
||||
### 3.2 Priority 规则
|
||||
|
||||
```yaml
|
||||
urgent:
|
||||
patterns:
|
||||
- "紧急"
|
||||
- "urgent"
|
||||
- "ASAP"
|
||||
- "线上"
|
||||
- "production"
|
||||
- "数据丢失"
|
||||
- "数据泄露"
|
||||
- "安全"
|
||||
- "security"
|
||||
- "CVE"
|
||||
- "RCE"
|
||||
- "越权"
|
||||
|
||||
high:
|
||||
patterns:
|
||||
- "重要"
|
||||
- "阻塞"
|
||||
- "block"
|
||||
- "无法工作"
|
||||
- "完全不能用"
|
||||
- "high"
|
||||
- "所有用户"
|
||||
- "全员受影响"
|
||||
|
||||
low:
|
||||
patterns:
|
||||
- "minor"
|
||||
- "小问题"
|
||||
- "nice to have"
|
||||
- "低优"
|
||||
- "不急"
|
||||
- "建议"
|
||||
- "锦上添花"
|
||||
|
||||
# 默认 normal(无任何上述信号)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 置信度计算
|
||||
|
||||
```python
|
||||
confidence = 0.0
|
||||
signals = 0
|
||||
|
||||
# tracker 决策信号
|
||||
if title_match:
|
||||
confidence += 0.4
|
||||
signals += 1
|
||||
if body_match:
|
||||
confidence += 0.2
|
||||
signals += 1
|
||||
if multiple_match_conflict:
|
||||
confidence -= 0.15
|
||||
|
||||
# priority 决策信号
|
||||
if urgent_or_high_signal:
|
||||
confidence += 0.2
|
||||
signals += 1
|
||||
|
||||
# 关联 Issue 强信号
|
||||
if duplicate_score >= 0.7:
|
||||
confidence += 0.15
|
||||
signals += 1
|
||||
|
||||
# 描述长度(信息量)
|
||||
if len(description) < 20:
|
||||
confidence -= 0.2 # 信息不足
|
||||
|
||||
# AI 语义判断的额外加权
|
||||
if ai_semantic_decision:
|
||||
confidence += 0.1
|
||||
|
||||
# 归一化到 [0, 1]
|
||||
confidence = max(0, min(1, confidence))
|
||||
```
|
||||
|
||||
**阈值**:
|
||||
- `confidence >= 0.7` → 直接应用
|
||||
- `0.5 <= confidence < 0.7` → 应用但标记"建议复核"
|
||||
- `confidence < 0.5` → **不应用**,仅放入"待人工"队列
|
||||
|
||||
---
|
||||
|
||||
## 5. 关联 Issue 算法
|
||||
|
||||
### 5.1 文本预处理
|
||||
|
||||
```python
|
||||
def tokenize(text):
|
||||
# 中文:2-gram 字符切片
|
||||
# 英文:小写化 + 词形还原
|
||||
# 去停用词("的", "了", "the", "a", "an", ...)
|
||||
tokens = set()
|
||||
# ... implementation
|
||||
return tokens
|
||||
```
|
||||
|
||||
### 5.2 Jaccard 相似度
|
||||
|
||||
```python
|
||||
def jaccard(a: set, b: set) -> float:
|
||||
if not a or not b:
|
||||
return 0.0
|
||||
return len(a & b) / len(a | b)
|
||||
```
|
||||
|
||||
### 5.3 关联决策
|
||||
|
||||
| 相似度 | 决策 |
|
||||
|--------|------|
|
||||
| ≥ 0.7 且一方已关闭 | 推荐 mark as duplicate |
|
||||
| ≥ 0.7 双方都开 | 评论"可能与 #X 相关" |
|
||||
| 0.4 - 0.7 | 列入"可能相关",由人工判断 |
|
||||
| < 0.4 | 不关联 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 输出 Schema
|
||||
|
||||
完整分析报告遵循以下 JSON Schema(简化版):
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["repository", "analyzed_at", "total", "items"],
|
||||
"properties": {
|
||||
"repository": {"type": "string", "pattern": "^[^/]+/[^/]+$"},
|
||||
"analyzed_at": {"type": "string", "format": "date-time"},
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"by_tracker": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "integer"}
|
||||
},
|
||||
"by_priority": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "integer"}
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["number", "title", "decisions", "confidence"],
|
||||
"properties": {
|
||||
"number": {"type": "integer"},
|
||||
"title": {"type": "string"},
|
||||
"current_tracker": {"type": ["string", "null"]},
|
||||
"current_labels": {"type": "array", "items": {"type": "string"}},
|
||||
"decisions": {
|
||||
"type": "object",
|
||||
"required": ["tracker", "priority"],
|
||||
"properties": {
|
||||
"tracker": {"type": "string", "enum": ["bug", "feature", "support", "doc", "test", "duplicate", "question"]},
|
||||
"priority": {"type": "string", "enum": ["low", "normal", "high", "urgent"]},
|
||||
"labels": {"type": "array", "items": {"type": "string"}},
|
||||
"assignee": {"type": ["string", "null"]},
|
||||
"related_issues": {"type": "array", "items": {"type": "integer"}},
|
||||
"mark_duplicate": {"type": ["integer", "null"]}
|
||||
}
|
||||
},
|
||||
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
|
||||
"reasoning": {"type": "string"},
|
||||
"matched_rules": {"type": "array", "items": {"type": "string"}},
|
||||
"needs_review": {"type": "boolean"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 边界情况处理
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| Issue 无正文 | confidence 上限 0.5;强制 needs_review=true |
|
||||
| 标题过长(> 100 字) | 截取前 50 字做匹配 |
|
||||
| 标题全英文 | 跳过中文规则,仅用英文规则 |
|
||||
| 仓库无任何标签 | 跳过 Stage D,在报告中提示 |
|
||||
| @mention 用户不在 assigners 列表 | 不指派,提示"权限不足" |
|
||||
| 历史 Issue < 5 个 | 跳过关联推荐 |
|
||||
| 已有 tracker 的 Issue | 默认不覆盖,除非用户加 `--force` |
|
||||
|
||||
---
|
||||
|
||||
## 8. 性能建议
|
||||
|
||||
| 规模 | 建议 |
|
||||
|------|------|
|
||||
| ≤ 20 个 Issue | 单次分析,内存缓存元数据 |
|
||||
| 20-100 个 | 分批 20/批,每批后用户确认 |
|
||||
| > 100 个 | 强制分批,每批 20,建议夜间运行 |
|
||||
|
||||
API 调用次数估算:`N * 1 (view) + 3 (元数据) + N * 0.3 (平均关联)` ≈ `1.3N + 3`。
|
||||
|
||||
---
|
||||
|
||||
## 9. 参考实现
|
||||
|
||||
伪代码(Python-like):
|
||||
|
||||
```python
|
||||
def triage_issue(issue, repo_meta, history):
|
||||
text = normalize(issue.subject + " " + issue.description)
|
||||
|
||||
# Stage B: tracker
|
||||
tracker, tracker_rules = decide_tracker(text)
|
||||
|
||||
# Stage C: priority
|
||||
priority, priority_rules = decide_priority(text)
|
||||
|
||||
# Stage D: labels
|
||||
labels = match_labels(tracker, priority, repo_meta.tags)
|
||||
|
||||
# Stage E: assignee
|
||||
assignee = parse_mention(issue.description, repo_meta.assigners)
|
||||
|
||||
# Stage F: related
|
||||
related, duplicate = find_related(issue, history)
|
||||
|
||||
# Stage G: confidence
|
||||
confidence = compute_confidence(
|
||||
tracker_rules, priority_rules, duplicate, len(issue.description)
|
||||
)
|
||||
|
||||
return {
|
||||
"number": issue.number,
|
||||
"decisions": {
|
||||
"tracker": tracker,
|
||||
"priority": priority,
|
||||
"labels": labels,
|
||||
"assignee": assignee,
|
||||
"related_issues": related,
|
||||
"mark_duplicate": duplicate,
|
||||
},
|
||||
"confidence": confidence,
|
||||
"matched_rules": tracker_rules + priority_rules,
|
||||
"needs_review": confidence < 0.7,
|
||||
}
|
||||
```
|
||||
|
||||
完整可运行实现请参考 [examples/triage-batch-workflow.md](../examples/triage-batch-workflow.md) 中的 AI Agent 提示词。
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
# gitlink-issue-triage — 应用变更手册
|
||||
|
||||
> 本文档说明如何把分析报告中的决策**安全地**应用到 GitLink Issue。
|
||||
> 所有命令默认 dry-run,确认后再去掉 `--dry-run` 实际执行。
|
||||
|
||||
## 1. 应用前置检查
|
||||
|
||||
### 1.1 备份当前状态
|
||||
|
||||
```bash
|
||||
# 导出当前所有目标 Issue 的原始字段(用于回滚)
|
||||
gitlink-cli issue +list --state open --format json > /tmp/before-triage.json
|
||||
```
|
||||
|
||||
### 1.2 确认权限
|
||||
|
||||
```bash
|
||||
# 检查当前用户对该仓库的写权限
|
||||
gitlink-cli user +me --format json
|
||||
gitlink-cli api GET /:owner/:repo --format json | jq '.data.permissions'
|
||||
```
|
||||
|
||||
若 `permissions.push !== true`,应用变更会失败,应停止并提示用户。
|
||||
|
||||
---
|
||||
|
||||
## 2. 单 Issue 应用流程
|
||||
|
||||
针对报告中的每个 item:
|
||||
|
||||
### 2.1 应用 tracker(类型)
|
||||
|
||||
```bash
|
||||
# 注意:GitLink v1 API 通过 tracker_id 字段更新
|
||||
gitlink-cli issue +update \
|
||||
--owner <owner> \
|
||||
--repo <repo> \
|
||||
--number <N> \
|
||||
--state in-progress # 顺带把状态从 new 改为 in-progress
|
||||
```
|
||||
|
||||
> ⚠️ **当前 gitlink-cli 的 `+update` 不直接支持改 tracker**。
|
||||
> 如需改 tracker,使用 Raw API:
|
||||
>
|
||||
> ```bash
|
||||
> # tracker_id: 1=bug, 2=feature, 3=support, 4=doc, 5=test, 6=duplicate, 7=question
|
||||
> gitlink-cli api PATCH /v1/<owner>/<repo>/issues/<N> \
|
||||
> --body '{"subject":"<原 subject>","description":"<原 description>","tracker_id":1}'
|
||||
> ```
|
||||
>
|
||||
> **必须**先 GET 当前 Issue 拿到 `subject` 和 `description`,否则会被清空。
|
||||
|
||||
### 2.2 应用 priority(优先级)
|
||||
|
||||
```bash
|
||||
# priority_id: 1=low, 2=normal, 3=high, 4=urgent
|
||||
gitlink-cli api PATCH /v1/<owner>/<repo>/issues/<N> \
|
||||
--body '{"subject":"<原>","description":"<原>","priority_id":3}'
|
||||
```
|
||||
|
||||
### 2.3 应用 labels(标签)
|
||||
|
||||
```bash
|
||||
# 方法 A:用 +label-add(推荐,自动处理 name→id)
|
||||
gitlink-cli issue +label-add \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--labels "缺陷,紧急"
|
||||
|
||||
# 方法 B:Raw API(需要预先查标签 ID)
|
||||
LABEL_IDS=$(echo "缺陷,紧急" | tr ',' '\n' | while read name; do
|
||||
gitlink-cli api GET /v1/<owner>/<repo>/issue_tags.json --format json \
|
||||
| jq -r --arg n "$name" '.data.issue_tags[] | select(.name==$argn) | .id'
|
||||
done | paste -sd, -)
|
||||
|
||||
gitlink-cli api POST /v1/<owner>/<repo>/issues/<N>/labels \
|
||||
--body "{\"labels\":\"$LABEL_IDS\"}"
|
||||
```
|
||||
|
||||
### 2.4 应用 assignee(指派人)
|
||||
|
||||
> ⚠️ **默认不自动指派个人**,除非用户明确同意。
|
||||
> 推荐做法:在评论中 @mention 建议由 PM 分配。
|
||||
|
||||
```bash
|
||||
# 若用户明确要求指派:
|
||||
gitlink-cli issue +update \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "<原 description>" # 占位,update 至少要改一个字段
|
||||
# 或通过 Raw API(更可控)
|
||||
USER_ID=$(gitlink-cli api GET /users/<login> --format json | jq '.data.id')
|
||||
gitlink-cli api PATCH /v1/<owner>/<repo>/issues/<N> \
|
||||
--body "{\"subject\":\"<原>\",\"description\":\"<原>\",\"assigned_to_id\":$USER_ID}"
|
||||
```
|
||||
|
||||
### 2.5 应用 comment(评论 + 关联 Issue)
|
||||
|
||||
```bash
|
||||
# 生成评论内容(Markdown)
|
||||
COMMENT_BODY=$(cat <<'EOF'
|
||||
🤖 **自动分类报告**
|
||||
|
||||
| 字段 | 决策 | 依据 |
|
||||
|------|------|------|
|
||||
| 类型 | bug | 标题含"无反应" |
|
||||
| 优先级 | high | 正文提"线上" |
|
||||
| 标签 | 缺陷, 紧急 | 仓库标签匹配 |
|
||||
|
||||
**关联 Issue**:可能与 #138("登录页加载失败")相关。
|
||||
|
||||
如分类有误请回复 `/triage incorrect`,我会重新分析。
|
||||
EOF
|
||||
)
|
||||
|
||||
gitlink-cli issue +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "$COMMENT_BODY"
|
||||
```
|
||||
|
||||
### 2.6 标记 duplicate(可选)
|
||||
|
||||
```bash
|
||||
# 仅评论建议,不主动关闭
|
||||
gitlink-cli issue +comment \
|
||||
--number <N> \
|
||||
--body "检测到本 Issue 与 #138 高度相似(相似度 0.82),建议维护者判断是否标记为重复。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 批量应用模板
|
||||
|
||||
### 3.1 Shell 脚本(推荐)
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# apply-triage.sh — 从 report.json 应用分类决策
|
||||
set -euo pipefail
|
||||
|
||||
OWNER="${1:?usage: apply-triage.sh <owner>/<repo> <report.json>}"
|
||||
REPO="${2:?missing repo}"
|
||||
REPORT="${3:?missing report.json}"
|
||||
|
||||
# 读取报告
|
||||
TOTAL=$(jq '.total' "$REPORT")
|
||||
echo "Will apply triage decisions to $TOTAL issues in $OWNER/$REPO"
|
||||
read -rp "Proceed? (yes/no) " CONFIRM
|
||||
[ "$CONFIRM" = "yes" ] || { echo "aborted"; exit 1; }
|
||||
|
||||
# 逐条应用
|
||||
jq -c '.items[]' "$REPORT" | while read -r item; do
|
||||
NUM=$(echo "$item" | jq '.number')
|
||||
TRACKER=$(echo "$item" | jq -r '.decisions.tracker')
|
||||
PRIORITY=$(echo "$item" | jq -r '.decisions.priority')
|
||||
CONF=$(echo "$item" | jq '.confidence')
|
||||
|
||||
echo "→ Issue #$NUM (tracker=$TRACKER, priority=$PRIORITY, conf=$CONF)"
|
||||
|
||||
# 跳过低置信度
|
||||
if (( $(echo "$CONF < 0.5" | bc -l) )); then
|
||||
echo " skipped (low confidence)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# ... 调用上面的应用命令
|
||||
|
||||
# 避免限流
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
echo "Done. Summary written to /tmp/after-triage.json"
|
||||
```
|
||||
|
||||
### 3.2 AI Agent 执行模板
|
||||
|
||||
向 Claude Code 发送:
|
||||
|
||||
```
|
||||
请按以下步骤应用 /tmp/triage-report.json 中的决策:
|
||||
|
||||
1. 读取报告,过滤 confidence < 0.5 的项
|
||||
2. 对每个剩余项:
|
||||
a. 用 Raw API PATCH 更新 tracker_id 和 priority_id(注意保留 subject/description)
|
||||
b. 用 issue +label-add 添加 labels
|
||||
c. 用 issue +comment 评论分析摘要
|
||||
3. 每应用 5 个后暂停,问我是否继续
|
||||
4. 完成后输出统计:成功数、失败数、跳过数
|
||||
|
||||
任何步骤失败都不要继续,停下来问我。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 回滚策略
|
||||
|
||||
### 4.1 自动备份
|
||||
|
||||
应用前已执行:
|
||||
|
||||
```bash
|
||||
gitlink-cli issue +list --state all --format json > /tmp/before-triage-$(date +%s).json
|
||||
```
|
||||
|
||||
### 4.2 回滚单 Issue
|
||||
|
||||
```bash
|
||||
# 从备份恢复原始字段
|
||||
ORIGINAL=$(jq '.data.issues[] | select(.number==142)' /tmp/before-triage.json)
|
||||
gitlink-cli api PATCH /v1/<owner>/<repo>/issues/142 \
|
||||
--body "$(echo "$ORIGINAL" | jq '{subject, description, tracker_id, priority_id, status_id}')"
|
||||
|
||||
# 移除新加的标签
|
||||
gitlink-cli issue +label-remove --number 142 --label "缺陷"
|
||||
gitlink-cli issue +label-remove --number 142 --label "紧急"
|
||||
```
|
||||
|
||||
### 4.3 批量回滚
|
||||
|
||||
```bash
|
||||
# 反向应用 before-triage.json,把每个 Issue 恢复到原始状态
|
||||
# 谨慎:会丢失 triage 之后的人工修改
|
||||
./apply-triage-rollback.sh <owner>/<repo> /tmp/before-triage.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误处理
|
||||
|
||||
| 错误 | 原因 | 处理 |
|
||||
|------|------|------|
|
||||
| `HTTP 401` | Token 失效 | `gitlink-cli auth login` |
|
||||
| `HTTP 403` | 无写权限 | 联系仓库 owner |
|
||||
| `HTTP 404` | Issue 编号错或已删除 | 跳过,记录到 errors |
|
||||
| `HTTP 422` | subject/description 被清空 | 必须先 GET 再 PATCH |
|
||||
| `status: -1` | 参数错 | 检查 tracker_id/priority_id 数值 |
|
||||
|
||||
应用失败时**不要重试**,记录到错误日志,整体应用结束后人工排查。
|
||||
|
||||
---
|
||||
|
||||
## 6. 审计日志
|
||||
|
||||
每次应用后记录:
|
||||
|
||||
```json
|
||||
{
|
||||
"applied_at": "2026-06-16T10:30:00Z",
|
||||
"operator": "ai-agent + human-confirm",
|
||||
"batch_id": "triage-20260616-1",
|
||||
"items_applied": [
|
||||
{
|
||||
"number": 142,
|
||||
"changes": {
|
||||
"tracker_id": {"from": null, "to": 1},
|
||||
"priority_id": {"from": 2, "to": 3},
|
||||
"labels_added": ["缺陷", "紧急"]
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
保存到 `/tmp/triage-audit-<timestamp>.json`,便于追溯。
|
||||
|
||||
---
|
||||
|
||||
## 7. 最佳实践
|
||||
|
||||
- ✅ **小批量试水**:先对 3-5 个 Issue 应用,观察结果再扩大
|
||||
- ✅ **敏感词过滤**:对 urgent 决策额外人工复核
|
||||
- ✅ **避开高峰**:大批量应用安排在用户活跃低谷时段
|
||||
- ✅ **通知 owner**:通过 `issue +comment` 在首个 Issue 中说明"本批为自动分类"
|
||||
- ❌ **禁止**:跳过 dry-run 直接批量应用
|
||||
- ❌ **禁止**:对 archived 或 read-only 仓库执行
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: gitlink-issue
|
||||
version: 2.0.0
|
||||
description: "Issue 管理:创建、查看、更新、关闭/批量关闭 Issue,添加评论。当用户需要操作 GitLink Issue 时触发。"
|
||||
version: 3.0.0
|
||||
description: "Issue 全生命周期管理:创建/查看/更新/关闭/重开 Issue、添加评论、标签操作(添加/移除/查看)、批量操作(创建/关闭/标签/状态/优先级/负责人)。当用户需要操作 GitLink Issue 时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
|
|
@ -18,42 +18,75 @@ metadata:
|
|||
|
||||
## Shortcuts
|
||||
|
||||
### 查询
|
||||
|
||||
| Shortcut | 说明 | 需要认证 |
|
||||
|----------|------|----------|
|
||||
| `issue +list` | Issue 列表 | 否(公开项目) |
|
||||
| `issue +create` | 创建 Issue | 是 |
|
||||
| `issue +view` | Issue 详情 | 否(公开项目) |
|
||||
| `issue +update` | 更新 Issue | 是 |
|
||||
| `issue +list` | Issue 列表(支持 `--state open/closed`、`--limit`、`--page`) | 否(公开项目) |
|
||||
| `issue +view` | Issue 详情(含 description、状态、优先级等) | 否(公开项目) |
|
||||
| `issue +label-list` | 查看 Issue 上的标签 | 否(公开项目) |
|
||||
|
||||
### 单个操作
|
||||
|
||||
| Shortcut | 说明 | 需要认证 |
|
||||
|----------|------|----------|
|
||||
| `issue +create` | 创建 Issue(`--title` + `--body`) | 是 |
|
||||
| `issue +update` | 更新 Issue 标题/描述 | 是 |
|
||||
| `issue +close` | 关闭 Issue | 是 |
|
||||
| `issue +batch-close` | 批量关闭 Issue,支持 `--dry-run` 预览 | 是(dry-run 不写入) |
|
||||
| `issue +reopen` | 重新打开已关闭的 Issue | 是 |
|
||||
| `issue +comment` | 添加评论 | 是 |
|
||||
| `issue +label-add` | 添加标签(⚠️ API 可能 404,建议用 `+batch-label`) | 是 |
|
||||
| `issue +label-remove` | 移除标签 | 是 |
|
||||
|
||||
### 批量操作
|
||||
|
||||
| Shortcut | 说明 | 支持 dry-run |
|
||||
|----------|------|-------------|
|
||||
| `issue +batch-create` | 批量创建 Issue(`--titles` 逗号分隔 或 `--from CSV`) | ✅ |
|
||||
| `issue +batch-close` | 批量关闭 Issue | ✅ |
|
||||
| `issue +batch-label` | 批量修改标签:bug, feature, support, doc, test, duplicate, question | ✅ |
|
||||
| `issue +batch-status` | 批量修改状态:new, in-progress, resolved, closed, rejected | ✅ |
|
||||
| `issue +batch-priority` | 批量修改优先级:low, normal, high, urgent | ✅ |
|
||||
| `issue +batch-assign` | 批量修改负责人(`--assignee` 登录名或用户 ID) | ✅ |
|
||||
|
||||
> 批量操作均支持 `--numbers 1,2,3` 或 `--from file.csv` 指定目标 Issue。
|
||||
|
||||
## 使用示例
|
||||
|
||||
```bash
|
||||
# === 查询 ===
|
||||
# 列出 Issue
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --state open
|
||||
|
||||
# 创建 Issue
|
||||
gitlink-cli issue +create --owner myuser --repo myrepo --title "Bug: 登录失败" --body "复现步骤:..."
|
||||
|
||||
# 查看 Issue 详情(使用网页可见的 Issue 编号)
|
||||
# 分页拉取
|
||||
gitlink-cli issue +list --owner Gitlink --repo forgeplus --state open --limit 20 --page 2
|
||||
# 查看详情(使用网页 URL 中的 Issue 编号)
|
||||
gitlink-cli issue +view --owner Gitlink --repo forgeplus --number 4
|
||||
|
||||
# === 单个操作 ===
|
||||
# 创建 Issue
|
||||
gitlink-cli issue +create --owner myuser --repo myrepo --title "Bug: 登录失败" --body "复现步骤:..."
|
||||
# 更新 Issue
|
||||
gitlink-cli issue +update --number 4 --title "新标题" --body "更新描述"
|
||||
|
||||
# 关闭 Issue
|
||||
# 关闭 / 重开
|
||||
gitlink-cli issue +close --number 4
|
||||
|
||||
# 预览批量关闭 Issue,不修改数据
|
||||
gitlink-cli issue +batch-close --owner myuser --repo myrepo --numbers 123,124 --dry-run
|
||||
|
||||
# 从 CSV 文件批量关闭 Issue
|
||||
gitlink-cli issue +batch-close --owner myuser --repo myrepo --from issues.csv
|
||||
|
||||
gitlink-cli issue +reopen --number 4
|
||||
# 添加评论
|
||||
gitlink-cli issue +comment --number 4 --body "已修复,请验证"
|
||||
|
||||
# === 批量操作 ===
|
||||
# 批量创建
|
||||
gitlink-cli issue +batch-create --titles "修复登录Bug,新增导出功能,优化首页加载"
|
||||
# 批量关闭(先 dry-run 预览)
|
||||
gitlink-cli issue +batch-close --numbers 1,2,3 --dry-run
|
||||
gitlink-cli issue +batch-close --numbers 1,2,3
|
||||
# 批量打标签
|
||||
gitlink-cli issue +batch-label --label duplicate --numbers 3,4
|
||||
# 批量改状态
|
||||
gitlink-cli issue +batch-status --state resolved --numbers 1,2,3
|
||||
# 批量改优先级
|
||||
gitlink-cli issue +batch-priority --priority high --numbers 5,6
|
||||
# 批量分配
|
||||
gitlink-cli issue +batch-assign --assignee zzx-coder --numbers 7,8
|
||||
```
|
||||
|
||||
## Raw API 补充
|
||||
|
|
@ -80,17 +113,31 @@ gitlink-cli api POST /:owner/:repo/issues/series_update --body '{"ids":[1,2,3],"
|
|||
## API 注意事项
|
||||
|
||||
- **Issue 编号(`--number`)是网页 URL 中看到的序号**(如 `issues/4` 中的 `4`),不是数据库内部 ID
|
||||
- **批量关闭使用 `--numbers`,同样传网页 URL 中的 Issue 编号**,不是数据库内部 ID
|
||||
- **批量操作使用 `--numbers`,同样传网页 URL 中的 Issue 编号**,不是数据库内部 ID
|
||||
- Issue 操作使用 v1 API(`/api/v1/`),支持按 Issue 编号查询和操作
|
||||
- **创建 Issue 时 CLI 会自动设置 `status_id: 1`(新增)和 `priority_id: 2`(正常)**
|
||||
- **更新/关闭 Issue 时必须保留当前 `subject` 和 `description`**,即使只修改状态(CLI 会先读取当前 Issue 并自动带回)
|
||||
- v1 API 写操作必须使用 `access_token`(非 `token`)认证,CLI 已自动处理
|
||||
- **`issue +label-add` / `+label-remove` / `+label-list` 的 labels API(`POST /v1/.../issues/{N}/labels`)可能返回 404**。打标签请优先使用 `issue +batch-label`,它走 `updateIssueField` 而非 labels API
|
||||
|
||||
## Issue 状态映射(status_id)
|
||||
|
||||
| status_id | 名称 | 说明 |
|
||||
|-----------|------|------|
|
||||
| 1 | 新增 | 新建 Issue 的默认状态 |
|
||||
| 2 | 正在解决 | 处理中 |
|
||||
| 3 | 已解决 | 已修复 |
|
||||
| 5 | 关闭 | 关闭(`+close` 命令使用此值) |
|
||||
| status_id | 名称 | `+batch-status --state` 对应值 |
|
||||
|-----------|------|-------------------------------|
|
||||
| 1 | 新增 | `new` |
|
||||
| 2 | 正在解决 | `in-progress` |
|
||||
| 3 | 已解决 | `resolved` |
|
||||
| 5 | 关闭 | `closed` |
|
||||
| 6 | 已拒绝 | `rejected` |
|
||||
|
||||
## Issue 标签映射(tracker_id)
|
||||
|
||||
| 标签 | `+batch-label --label` 对应值 |
|
||||
|------|------------------------------|
|
||||
| Bug | `bug` |
|
||||
| 功能 | `feature` |
|
||||
| 支持 | `support` |
|
||||
| 文档 | `doc` |
|
||||
| 测试 | `test` |
|
||||
| 重复 | `duplicate` |
|
||||
| 问题 | `question` |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: gitlink-repo
|
||||
version: 1.0.0
|
||||
description: "仓库管理:创建、查看、Fork、删除仓库,查看分支、提交、贡献者等。当用户需要操作 GitLink 仓库时触发。"
|
||||
version: 2.0.0
|
||||
description: "仓库全生命周期管理:创建/查看/Fork/删除/更新仓库、成员管理(邀请/移除/查看)、批量操作(创建/更新/邀请/移除)。当用户需要操作 GitLink 仓库时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
|
|
@ -18,35 +18,72 @@ metadata:
|
|||
|
||||
## Shortcuts
|
||||
|
||||
### 查询
|
||||
|
||||
| Shortcut | 说明 | 需要认证 |
|
||||
|----------|------|----------|
|
||||
| `repo +list` | 仓库列表 | 否(公开项目) |
|
||||
| `repo +list` | 仓库列表(`--user` 指定用户) | 否(公开项目) |
|
||||
| `repo +info` | 仓库详情 | 否(公开项目) |
|
||||
| `repo +create` | 创建仓库 | 是 |
|
||||
| `repo +members` | 仓库成员列表(`--page`、`--limit` 分页) | 否(公开项目) |
|
||||
|
||||
### 单个操作
|
||||
|
||||
| Shortcut | 说明 | 需要认证 |
|
||||
|----------|------|----------|
|
||||
| `repo +create` | 创建仓库(`--name`、`--description`、`--private`) | 是 |
|
||||
| `repo +fork` | Fork 仓库 | 是 |
|
||||
| `repo +delete` | 删除仓库 | 是 |
|
||||
| `repo +delete` | 删除仓库(⚠️ 不可逆) | 是 |
|
||||
| `repo +update` | 更新仓库设置(`--description`、`--private`) | 是 |
|
||||
| `repo +invite` | 邀请成员(`--user-id`) | 是 |
|
||||
| `repo +remove-member` | 移除成员(`--user-id`) | 是 |
|
||||
|
||||
### 批量操作
|
||||
|
||||
| Shortcut | 说明 | 支持 dry-run |
|
||||
|----------|------|-------------|
|
||||
| `repo +batch-create` | 批量创建仓库(`--names` 逗号分隔 或 `--from CSV`) | ✅ |
|
||||
| `repo +batch-update` | 批量更新仓库设置(`--description`、`--private`/`--public`) | ✅ |
|
||||
| `repo +batch-invite` | 批量邀请成员(`--users` 逗号分隔 或 `--from CSV`) | ✅ |
|
||||
| `repo +batch-remove` | 批量移除成员(`--users` 逗号分隔 或 `--from CSV`) | ✅ |
|
||||
|
||||
> 批量操作均支持 `--names repo-a,repo-b` 或 `--users 1,2,3` 或 `--from file.csv` 三种输入方式。
|
||||
|
||||
## 使用示例
|
||||
|
||||
```bash
|
||||
# === 查询 ===
|
||||
# 查看仓库信息
|
||||
gitlink-cli repo +info --owner Gitlink --repo forgeplus
|
||||
|
||||
# 在 git 仓库目录下自动解析
|
||||
cd ~/my-project
|
||||
gitlink-cli repo +info
|
||||
|
||||
# 列出用户的仓库
|
||||
# 列出用户仓库
|
||||
gitlink-cli repo +list --user zhangsan
|
||||
# 查看成员
|
||||
gitlink-cli repo +members --owner myuser --repo myrepo
|
||||
|
||||
# === 单个操作 ===
|
||||
# 创建仓库
|
||||
gitlink-cli repo +create --name my-project --description "项目描述"
|
||||
|
||||
# 创建私有仓库
|
||||
gitlink-cli repo +create --name my-project --private
|
||||
# Fork 仓库
|
||||
gitlink-cli repo +fork --owner Gitlink --repo forgeplus
|
||||
|
||||
# 删除仓库(⚠️ 危险操作)
|
||||
# 更新仓库设置
|
||||
gitlink-cli repo +update --owner myuser --repo myrepo --description "新描述"
|
||||
gitlink-cli repo +update --owner myuser --repo myrepo --private true
|
||||
# 邀请/移除成员
|
||||
gitlink-cli repo +invite --owner myuser --repo myrepo --user-id 12345
|
||||
gitlink-cli repo +remove-member --owner myuser --repo myrepo --user-id 12345
|
||||
# 删除仓库(⚠️ 不可逆,务必确认)
|
||||
gitlink-cli repo +delete --owner myuser --repo old-project
|
||||
|
||||
# === 批量操作 ===
|
||||
# 批量创建
|
||||
gitlink-cli repo +batch-create --names repo-a,repo-b,repo-c --private
|
||||
# 批量更新(先 dry-run 预览)
|
||||
gitlink-cli repo +batch-update --names repo-a,repo-b --description "批量更新描述" --dry-run
|
||||
gitlink-cli repo +batch-update --names repo-a,repo-b --description "批量更新描述"
|
||||
# 批量管理成员
|
||||
gitlink-cli repo +batch-invite --users 111,222,333 --dry-run
|
||||
gitlink-cli repo +batch-remove --users 111,222 --dry-run
|
||||
```
|
||||
|
||||
## Raw API 补充
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
# gitlink-stale
|
||||
|
||||
> GitLink Stale Issue/PR 自动处理 Skill — 让 AI Agent 帮你清理堆积如山的未活动 Issue/PR
|
||||
|
||||
[](./SKILL.md)
|
||||
[](https://claude.com/claude-code)
|
||||
|
||||
## 🎯 这是什么?
|
||||
|
||||
`gitlink-stale` 是基于 [gitlink-cli](../../README.md) 的 **AI Agent Skill**,专门用于:
|
||||
|
||||
- 🔍 **扫描识别** 长期未活动的 GitLink Issue / PR(默认 60 天)
|
||||
- 🧠 **AI 智能判断** 区分"真僵尸"和"等维护者回复"(不是简单按时间一刀切)
|
||||
- 🏷️ **自动打 stale 标签** 通知作者/相关者
|
||||
- 💬 **友好催办评论** 避免简单粗暴的"过期警告"
|
||||
- 🔒 **过期自动关闭** 超过宽限期(默认 14 天)仍未响应才关闭
|
||||
- 🛡️ **白名单豁免** `pinned`/`security`/`roadmap` 永不动
|
||||
- 📊 **生成审计报告** JSON + 表格,可追溯
|
||||
|
||||
适合**所有 Issue/PR 长期堆积**的开源项目或团队仓库,承担类似 GitHub `stale` bot 的角色,但通过 AI Agent 实现"人在环路"和"智能判断"。
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 前置条件
|
||||
|
||||
1. 已安装 `gitlink-cli`(参考 [主 README](../../README.md#安装与快速上手))
|
||||
2. 已完成认证(`gitlink-cli auth login`)
|
||||
3. 在目标仓库目录下(自动解析 owner/repo)或显式指定 `--owner --repo`
|
||||
|
||||
### 5 分钟体验
|
||||
|
||||
向 AI Agent(如 Claude Code)说:
|
||||
|
||||
> "帮我用 gitlink-stale 扫描 owner/repo 仓库中所有 60 天以上未活动的 open Issue,生成报告后等我确认。"
|
||||
|
||||
AI 会:
|
||||
|
||||
1. 拉取所有 open Issue/PR
|
||||
2. 按时间过滤 + 白名单豁免 + AI 判断
|
||||
3. 展示表格报告(含 confidence)
|
||||
4. 等你确认后才执行 mark_stale / auto_close 动作
|
||||
|
||||
---
|
||||
|
||||
## 📁 Skill 结构
|
||||
|
||||
```
|
||||
gitlink-stale/
|
||||
├── README.md # 本文件
|
||||
├── SKILL.md # AI Agent 读取的主入口
|
||||
├── references/
|
||||
│ ├── gitlink-stale-scan.md # 扫描算法详解
|
||||
│ ├── gitlink-stale-judge.md # AI 判断规则详解
|
||||
│ ├── gitlink-stale-actions.md # 动作执行手册
|
||||
│ └── gitlink-stale-exempt.md # 白名单豁免规则
|
||||
├── examples/
|
||||
│ ├── weekly-cleanup-workflow.md # 每周清理工作流
|
||||
│ ├── pr-stale-workflow.md # PR 催办工作流
|
||||
│ └── ai-judgment-demo.md # AI 判断示例
|
||||
└── skill_test.md # 测试指南
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧠 核心差异化(vs GitHub stale-bot)
|
||||
|
||||
| 维度 | GitHub stale-bot | gitlink-stale(本 Skill) |
|
||||
|------|-----------------|------------------------|
|
||||
| **触发** | 事件驱动(cron),自动跑 | Agent 驱动(按需),人在环路 |
|
||||
| **判断** | 仅看时间(>= 60 天) | 时间 + AI 判断"真僵尸" |
|
||||
| **白名单** | 简单 label 匹配 | 多信号(label + tracker + priority + 作者活跃度) |
|
||||
| **评论** | 固定模板 | 根据上下文动态生成 |
|
||||
| **回滚** | 难(已自动关闭) | 字段快照,一键恢复 |
|
||||
| **审计** | 日志在 Actions | JSON 报告 + 备份文件 |
|
||||
|
||||
**关键差异**:本 Skill 不是简单按时间一刀切,而是通过 AI 判断评论历史、活跃度等信号决定"是否真应该处理"。
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 安全设计
|
||||
|
||||
| 机制 | 说明 |
|
||||
|------|------|
|
||||
| ✅ Dry-run 默认 | 分析阶段不调用任何写 API |
|
||||
| ✅ 双重确认 | 应用动作前必须表格展示 + 用户同意 |
|
||||
| ✅ 白名单豁免 | pinned/security/roadmap 永不动 |
|
||||
| ✅ AI 双重判断 | 不仅看时间,还要 AI 判断"真僵尸" |
|
||||
| ✅ 低置信度跳过 | confidence < 0.6 不自动处理 |
|
||||
| ✅ 字段快照 | 每个变更保留原始标签和状态,支持回滚 |
|
||||
| ✅ 批次上限 | 单批 ≤ 20 个,超出强制分批 |
|
||||
|
||||
---
|
||||
|
||||
## 🤖 AI Agent 兼容性
|
||||
|
||||
已在以下 Agent 平台设计兼容:
|
||||
|
||||
- ✅ **Claude Code** — 主要验证目标,所有示例均可执行
|
||||
- ✅ **Cursor** — 通过 SKILL.md markdown 协议兼容
|
||||
- ✅ **OpenAI Code** — 通过 references/ 文档兼容
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [SKILL.md — AI Agent 主入口](./SKILL.md)
|
||||
- [扫描算法详解](./references/gitlink-stale-scan.md)
|
||||
- [AI 判断规则详解](./references/gitlink-stale-judge.md)
|
||||
- [动作执行手册](./references/gitlink-stale-actions.md)
|
||||
- [白名单豁免规则](./references/gitlink-stale-exempt.md)
|
||||
- [每周清理工作流示例](./examples/weekly-cleanup-workflow.md)
|
||||
- [PR 催办示例](./examples/pr-stale-workflow.md)
|
||||
- [AI 判断演示](./examples/ai-judgment-demo.md)
|
||||
- [测试指南](./skill_test.md)
|
||||
- [上游 Skill: gitlink-issue](../gitlink-issue/SKILL.md)
|
||||
- [互补 Skill: gitlink-issue-triage](../gitlink-issue-triage/SKILL.md)
|
||||
- [共享规则: gitlink-shared](../gitlink-shared/SKILL.md)
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
**Q: 必须用 AI Agent 吗?人能用吗?**
|
||||
A: 当然可以。SKILL.md 中的工作流对人类也是清晰的 SOP,你可以手动按步骤执行 gitlink-cli 命令。AI 的价值在 Stage C "真假僵尸判断",但人类读评论历史同样能做。
|
||||
|
||||
**Q: PR 没有 label 接口怎么打 stale?**
|
||||
A: GitLink PR 端点暂不支持 PR 维度的标签。对 PR 只做评论催办,在评论标题写"⏰ Stale"作为视觉提示。
|
||||
|
||||
**Q: 用户回复后会自动去掉 stale 标签吗?**
|
||||
A: 默认不会自动响应。下次扫描时看到新活动会自动跳过;如需立刻移除,手动调用 `issue +label-remove`。
|
||||
|
||||
**Q: 与 GitHub Actions 的 stale-bot 有何不同?**
|
||||
A: 本 Skill 是 **Agent-driven**(按需触发、人在环路、AI 智能判断),不是 **Event-driven**(自动触发、机械规则)。适合需要人工监督和精准判断的高质量项目。
|
||||
|
||||
**Q: 误关了重要 Issue 怎么办?**
|
||||
A: 见 SKILL.md §8 回滚策略。所有动作都保留原始字段快照,可重新打开。强烈建议 urgent/roadmap 类 Issue 打上对应标签加入白名单。
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
继承 gitlink-cli 的 [MulanPSL-2.0](../../LICENSE)。
|
||||
|
|
@ -0,0 +1,452 @@
|
|||
---
|
||||
name: gitlink-stale
|
||||
version: 1.0.0
|
||||
description: "Stale Issue/PR 自动处理:识别长期未活动的 Issue/PR,标记 stale 标签、通知相关者、过期自动关闭。当用户需要清理堆积 Issue/PR、定期巡检仓库、或想仿照 GitHub stale-bot 行为时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
cliHelp: "gitlink-cli issue --help"
|
||||
---
|
||||
|
||||
# gitlink-stale(Stale Issue/PR 自动处理)
|
||||
|
||||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有"标记/评论/关闭"动作默认 dry-run;只有用户明确确认后才执行写入。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。**
|
||||
|
||||
> **前置依赖:** 先阅读 [`../gitlink-issue/SKILL.md`](../gitlink-issue/SKILL.md) 了解 Issue 基础操作和字段映射,[`../gitlink-pr/SKILL.md`](../gitlink-pr/SKILL.md) 了解 PR 基础操作。
|
||||
|
||||
---
|
||||
|
||||
## 1. 这个 Skill 做什么?
|
||||
|
||||
`gitlink-stale` 是一个 **AI Agent 驱动的长期未活动 Issue/PR 处理工作流**,解决开源/协作项目中常见的"Issue/PR 堆积无人理"问题:
|
||||
|
||||
- 🔍 **扫描识别**:找出 N 天未活动的 Issue/PR(默认 60 天)
|
||||
- 🧠 **AI 智能判断**:区分"真僵尸"和"等维护者回复"(不是简单按时间一刀切)
|
||||
- 🏷️ **自动打 stale 标签**:通知作者/相关者
|
||||
- 💬 **友好催办评论**:避免简单粗暴的"过期警告"
|
||||
- 🔒 **过期自动关闭**:超过宽限期(默认 14 天)仍未响应才关闭
|
||||
- 🛡️ **白名单豁免**:`pinned`/`security`/`roadmap` 等关键 Issue 永不处理
|
||||
- ✅ **Dry-run 优先**:所有写入操作默认预览,确认后才落地
|
||||
|
||||
适合**所有 Issue/PR 长期堆积**的开源项目或团队仓库,承担类似 GitHub `stale` bot 的角色,但通过 AI Agent 实现"人在环路"和"智能判断"。
|
||||
|
||||
---
|
||||
|
||||
## 2. 工作流总览
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Step 1: 扫描候选列表 │
|
||||
│ issue +list --state open │
|
||||
│ pr +list --state open │
|
||||
└────────────┬────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Step 2: 时间过滤 │
|
||||
│ now - updated_at > stale_days (默认 60) │
|
||||
│ 排除白名单(pinned/security/roadmap/...) │
|
||||
└────────────┬─────────────────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Step 3: AI 智能分类(核心差异化) │
|
||||
│ - 是否仍在等维护者回复? │
|
||||
│ - 是否是关键功能/路线图? │
|
||||
│ - 是否历史活跃度高? │
|
||||
│ - 输出 confidence + recommendation │
|
||||
└────────────┬─────────────────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Step 4: 生成处理计划(dry-run) │
|
||||
│ { │
|
||||
│ issue_id: 142, │
|
||||
│ action: "mark_stale", │
|
||||
│ reason: "60 天未活动", │
|
||||
│ confidence: 0.85 │
|
||||
│ } │
|
||||
└────────────┬─────────────────────────────────┘
|
||||
▼
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Step 5: 用户确认 → 执行 │
|
||||
│ issue +label-add --label stale │
|
||||
│ issue +comment --body "..." │
|
||||
│ 过宽限期: issue +close │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Shortcuts 与 Raw API 速查
|
||||
|
||||
本 Skill 复用 gitlink-cli 已有命令,**不新增 shortcut**,确保单一可信源。
|
||||
|
||||
### 3.1 读操作(只读,可放心使用)
|
||||
|
||||
| 命令 | 用途 |
|
||||
|------|------|
|
||||
| `issue +list --state open --format json` | 获取 open Issue 列表 |
|
||||
| `issue +view --number <N> --format json` | 获取 Issue 详情(含 journals 评论历史) |
|
||||
| `issue +label-list --number <N> --format json` | 查看 Issue 当前标签 |
|
||||
| `pr +list --state open --format json` | 获取 open PR 列表(注意需客户端按 `pull_request_status: 0` 二次过滤) |
|
||||
| `pr +view --id <N> --format json` | 获取 PR 详情 |
|
||||
| `api GET /v1/:owner/:repo/issue_tags.json` | 获取仓库标签(name→id 映射) |
|
||||
|
||||
### 3.2 写操作(默认 dry-run,确认后执行)
|
||||
|
||||
| 命令 | 用途 |
|
||||
|------|------|
|
||||
| `issue +label-add --number <N> --labels "stale"` | 给 Issue 打 stale 标签 |
|
||||
| `issue +label-remove --number <N> --label "stale"` | 移除 stale 标签(用户回复后恢复) |
|
||||
| `issue +comment --number <N> --body "<text>"` | 评论催办/关闭说明 |
|
||||
| `issue +close --number <N>` | 关闭 Issue |
|
||||
| `issue +batch-close --numbers <csv> --dry-run` | 批量关闭预览 |
|
||||
| `pr +comment --id <N> --body "<text>"` | 给 PR 评论 |
|
||||
|
||||
> ⚠️ **PR 标签**:GitLink PR 端点暂不支持 PR 维度的 label 操作,对 PR 只评论、不打标签;若需要"PR stale 标签",在评论正文中显式写"⚠️ Stale"。
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心算法(AI 智能判断)
|
||||
|
||||
> 这是本 Skill 与"简单按时间一刀切"工具的核心差异。
|
||||
> AI Agent 应**优先**遵循以下规则,对规则无法覆盖的情况使用语义判断。
|
||||
|
||||
### 4.1 三阶段判断流水线
|
||||
|
||||
```
|
||||
Issue/PR JSON
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage A: 时间扫描 │
|
||||
│ - days_inactive = now - updated │
|
||||
│ - 默认阈值: 60 天 │
|
||||
└────────────┬────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage B: 白名单豁免 │
|
||||
│ - 含 pinned/security 等标签 → 跳过│
|
||||
│ - tracker 是 roadmap/epic → 跳过 │
|
||||
└────────────┬────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Stage C: AI 真假僵尸判断 │
|
||||
│ - 评论历史分析 │
|
||||
│ - 等维护者回复?等用户回复? │
|
||||
│ - 输出 truly_stale + confidence │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 时间计算
|
||||
|
||||
**关键字段**:`updated_at`(Issue 最后活动时间,包括评论、状态变更、字段修改)
|
||||
|
||||
```python
|
||||
days_inactive = (now_utc - parse(issue.updated_at)).days
|
||||
|
||||
# 阈值(可通过参数自定义)
|
||||
if days_inactive >= close_threshold: # 默认 74 天(60 + 14 宽限期)
|
||||
candidate_action = "auto_close"
|
||||
elif days_inactive >= stale_threshold: # 默认 60 天
|
||||
candidate_action = "mark_stale"
|
||||
else:
|
||||
candidate_action = None # 不处理
|
||||
```
|
||||
|
||||
> ⚠️ **GitLink API 已知行为**:`updated_at` 字段在某些 Issue 上可能缺失或时区异常。降级策略:取 `journals` 数组最后一条的 `created_at` 作为最后活动时间。
|
||||
|
||||
### 4.3 白名单豁免规则
|
||||
|
||||
满足**任一**条件即跳过处理:
|
||||
|
||||
| 信号 | 说明 |
|
||||
|------|------|
|
||||
| 含 `pinned`/`置顶` 标签 | 重要 Issue |
|
||||
| 含 `security`/`安全` 标签 | 安全相关 |
|
||||
| 含 `roadmap`/`路线图` 标签 | 长期规划 |
|
||||
| 含 `epic`/`里程碑` 标签 | 大任务 |
|
||||
| tracker_id 是 `roadmap` 类型 | 路线图类 |
|
||||
| priority_id == 4 (urgent) | 紧急任务 |
|
||||
| 标题含 `[Keep Open]`/`[Pinned]` | 显式标记 |
|
||||
| 作者仍是仓库活跃成员 | 信任作者会跟进 |
|
||||
|
||||
### 4.4 AI 真假僵尸判断(Stage C 核心)
|
||||
|
||||
**关键差异化**:不是简单的"时间到了就标记",而是 AI 判断是否真的应该处理。
|
||||
|
||||
**判断信号**:
|
||||
|
||||
| 信号类型 | 僵尸(应处理) | 活跃(应跳过) |
|
||||
|---------|---------------|---------------|
|
||||
| 评论历史 | 维护者 0 回复,或仅"已知问题"占位 | 维护者最近 30 天内回复过 |
|
||||
| Issue 类型 | bug/feature/小需求,需要明确动作 | question/讨论类,已自然结束 |
|
||||
| 标签 | 无标签或仅 stale | `in-progress`/`under-review` |
|
||||
| 评论数 | 0 评论,长期无人理 | ≥5 评论,有讨论 |
|
||||
| 作者活跃度 | 0 issue 历史,可能是路过用户 | 资深贡献者,会跟进 |
|
||||
| 关键词 | "测试"、"占位"、"无复现" | "正在处理"、"待 v2"、"等待上游" |
|
||||
|
||||
**AI 决策输出**:
|
||||
|
||||
```json
|
||||
{
|
||||
"issue_number": 142,
|
||||
"title": "...",
|
||||
"last_activity": "2026-04-15T10:30:00Z",
|
||||
"days_inactive": 62,
|
||||
"ai_analysis": {
|
||||
"truly_stale": true,
|
||||
"confidence": 0.85,
|
||||
"reason": "用户最后回复 60 天前,维护者无回复,0 评论,作者仅此 1 个 Issue",
|
||||
"exempt": false
|
||||
},
|
||||
"recommended_action": "mark_stale",
|
||||
"next_review_date": "2026-06-30"
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 置信度阈值
|
||||
|
||||
| confidence | 建议动作 |
|
||||
|-----------|---------|
|
||||
| ≥ 0.8 | 直接列入"建议执行"清单 |
|
||||
| 0.6 - 0.8 | 列入"建议执行",但报告中标记"建议人工复核" |
|
||||
| < 0.6 | **不自动处理**,仅列入"待人工判断"队列 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 标准工作流(AI Agent 执行模板)
|
||||
|
||||
> **AI Agent 看这里**:以下是你被请求"清理 stale Issue/PR"时应遵循的标准流程。
|
||||
|
||||
### Step 1 — 确认范围与参数
|
||||
|
||||
```bash
|
||||
# 默认参数(可被用户覆盖)
|
||||
# - stale_threshold: 60 天
|
||||
# - close_threshold: 74 天(60 + 14 宽限期)
|
||||
# - batch_size: 20 个/批
|
||||
|
||||
# 确认 owner/repo
|
||||
gitlink-cli issue +list --state open --limit 5 --format json
|
||||
```
|
||||
|
||||
向用户确认:"要扫描哪个仓库?阈值用默认(60 天标记/74 天关闭)还是自定义?想一批处理多少个?"
|
||||
|
||||
### Step 2 — 拉取候选列表
|
||||
|
||||
```bash
|
||||
# Issue 候选
|
||||
gitlink-cli issue +list \
|
||||
--owner <owner> --repo <repo> \
|
||||
--state open \
|
||||
--limit 100 \
|
||||
--format json > /tmp/issues-open.json
|
||||
|
||||
# PR 候选
|
||||
gitlink-cli pr +list \
|
||||
--owner <owner> --repo <repo> \
|
||||
--state open \
|
||||
--format json > /tmp/prs-open.json
|
||||
```
|
||||
|
||||
### Step 3 — 拉取仓库标签(用于白名单判断)
|
||||
|
||||
```bash
|
||||
gitlink-cli api GET /v1/<owner>/<repo>/issue_tags.json --format json \
|
||||
> /tmp/repo-tags.json
|
||||
```
|
||||
|
||||
### Step 4 — 逐个分析
|
||||
|
||||
对每个候选项:
|
||||
|
||||
```bash
|
||||
# Issue 详情(含 journals)
|
||||
gitlink-cli issue +view --number <N> --format json
|
||||
|
||||
# PR 详情
|
||||
gitlink-cli pr +view --id <N> --format json
|
||||
```
|
||||
|
||||
应用第 4 节判断规则,生成分析结果。
|
||||
|
||||
### Step 5 — 汇总报告
|
||||
|
||||
把所有候选项的分析结果合并:
|
||||
|
||||
```json
|
||||
{
|
||||
"repository": "owner/repo",
|
||||
"scanned_at": "2026-06-23T10:00:00Z",
|
||||
"thresholds": {
|
||||
"stale_days": 60,
|
||||
"close_days": 74
|
||||
},
|
||||
"summary": {
|
||||
"total_open_issues": 85,
|
||||
"total_open_prs": 12,
|
||||
"stale_candidates": 23,
|
||||
"close_candidates": 8,
|
||||
"exempt": 15,
|
||||
"needs_review": 4
|
||||
},
|
||||
"items": [
|
||||
/* 每个候选项的详细分析 */
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**用表格形式向用户展示摘要**(人类可读),等待用户确认。
|
||||
|
||||
### Step 6 — 应用动作(用户确认后)
|
||||
|
||||
按推荐动作执行:
|
||||
|
||||
```bash
|
||||
# 动作 A:标记 stale
|
||||
gitlink-cli issue +label-add --number 142 --labels "stale"
|
||||
gitlink-cli issue +comment --number 142 --body "⏰ 本 Issue 已 60 天无活动..."
|
||||
|
||||
# 动作 B:自动关闭(超过宽限期)
|
||||
gitlink-cli issue +label-add --number 142 --labels "stale"
|
||||
gitlink-cli issue +comment --number 142 --body "🔒 本 Issue 已 74 天无活动,自动关闭..."
|
||||
gitlink-cli issue +close --number 142
|
||||
|
||||
# 动作 C:PR 催办(不打标签,仅评论)
|
||||
gitlink-cli pr +comment --id 8 --body "⏰ 本 PR 已 60 天无活动..."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 催办评论模板
|
||||
|
||||
### 6.1 标记 stale(友好版,非警告)
|
||||
|
||||
```markdown
|
||||
⏰ **长期未活动提醒**
|
||||
|
||||
本 Issue 已 60 天未收到新回复,暂时标记为 `stale`。
|
||||
|
||||
- 如果**仍然相关**,请回复任意内容,会自动移除 stale 标签
|
||||
- 如果**已经过时**,欢迎手动关闭
|
||||
- 如果在 **14 天内**没有新活动,将自动关闭以保持 Issue 列表清爽
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动生成。如有疑问请联系 @维护者。
|
||||
```
|
||||
|
||||
### 6.2 自动关闭(礼貌版)
|
||||
|
||||
```markdown
|
||||
🔒 **自动关闭(长期未活动)**
|
||||
|
||||
本 Issue 自标记 stale 后 14 天内仍未收到新回复,自动关闭。
|
||||
|
||||
- 如问题仍然存在,请**重新打开**并补充最新信息
|
||||
- 如需长期保留,可打上 `pinned` 标签豁免巡检
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动关闭。原始讨论保留在历史中。
|
||||
```
|
||||
|
||||
### 6.3 PR 催办
|
||||
|
||||
```markdown
|
||||
⏰ **PR 长期未活动**
|
||||
|
||||
本 PR 已 60 天未更新,可能存在以下情况:
|
||||
|
||||
- 合并遇到冲突?请 rebase 后重新推送
|
||||
- 等待 review?可 @mention 相关维护者
|
||||
- 不再需要?欢迎手动关闭
|
||||
|
||||
如果 **14 天内**没有新活动,将默认关闭。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 安全规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| ✅ **Dry-run 优先** | 分析阶段只读,不调用任何写 API |
|
||||
| ✅ **用户确认** | 应用变更前必须展示报告并征得同意 |
|
||||
| ✅ **白名单豁免** | pinned/security/roadmap 永不动 |
|
||||
| ✅ **AI 双重判断** | 不仅看时间,还要 AI 判断"真僵尸" |
|
||||
| ✅ **小批量** | 一批不超过 20 个,超 50 强制分批 |
|
||||
| ✅ **低置信度跳过** | confidence < 0.6 不自动处理 |
|
||||
| ✅ **可回滚** | 记录原始标签和状态,便于撤销 |
|
||||
| ❌ **禁止** | 批量关闭超过 50 个 Issue 而不分批确认 |
|
||||
| ❌ **禁止** | 跳过 dry-run 直接执行 |
|
||||
|
||||
---
|
||||
|
||||
## 8. 回滚策略
|
||||
|
||||
### 8.1 备份原始状态
|
||||
|
||||
```bash
|
||||
# 应用前导出当前 Issue 状态
|
||||
gitlink-cli issue +list --state open --format json > /tmp/before-stale-$(date +%s).json
|
||||
```
|
||||
|
||||
### 8.2 单 Issue 回滚
|
||||
|
||||
```bash
|
||||
# 误标的 Issue:移除 stale 标签 + 道歉评论
|
||||
gitlink-cli issue +label-remove --number 142 --label "stale"
|
||||
gitlink-cli issue +comment --number 142 --body "抱歉,刚刚的 stale 标记是误判,已恢复。"
|
||||
```
|
||||
|
||||
### 8.3 误关闭的 Issue 恢复
|
||||
|
||||
```bash
|
||||
# 重新打开(用 Raw API,因 gitlink-cli +update 需要状态参数)
|
||||
gitlink-cli api PATCH /v1/<owner>/<repo>/issues/142 \
|
||||
--body '{"subject":"<原>","description":"<原>","status_id":1}' # 1=open
|
||||
gitlink-cli issue +label-remove --number 142 --label "stale"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 与现有 Skills 的关系
|
||||
|
||||
| Skill | 关系 |
|
||||
|-------|------|
|
||||
| [`gitlink-shared`](../gitlink-shared/SKILL.md) | 前置必读:认证、错误处理、安全规则 |
|
||||
| [`gitlink-issue`](../gitlink-issue/SKILL.md) | 基础命令来源:所有写操作通过这里的 shortcut |
|
||||
| [`gitlink-pr`](../gitlink-pr/SKILL.md) | PR 操作来源 |
|
||||
| [`gitlink-issue-triage`](../gitlink-issue-triage/SKILL.md) | 互补:triage 处理"未分类",stale 处理"未活动" |
|
||||
|
||||
---
|
||||
|
||||
## 10. 参考文档
|
||||
|
||||
- [扫描算法详解](references/gitlink-stale-scan.md) — 时间计算、字段降级、批量策略
|
||||
- [AI 判断规则详解](references/gitlink-stale-judge.md) — 真假僵尸判断的完整信号集
|
||||
- [动作执行手册](references/gitlink-stale-actions.md) — 写操作命令清单、评论模板、回滚策略
|
||||
- [白名单豁免规则](references/gitlink-stale-exempt.md) — 哪些 Issue 永不处理
|
||||
- [每周清理工作流示例](examples/weekly-cleanup-workflow.md) — 端到端定期巡检
|
||||
- [PR 催办示例](examples/pr-stale-workflow.md) — PR 维度的处理流程
|
||||
- [AI 判断演示](examples/ai-judgment-demo.md) — 复杂 Issue 的判断示例
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题
|
||||
|
||||
**Q: 为什么不用一个固定的 stale-bot 配置文件?**
|
||||
A: 因为不同 Issue 的"重要程度"差异巨大。AI Agent 可以读取评论历史判断"是否还在等维护者",比规则引擎更准。
|
||||
|
||||
**Q: 用户回复后会自动去掉 stale 标签吗?**
|
||||
A: 默认不会自动响应。本 Skill 是按需触发(如每周巡检),下次扫描时会看到新活动并自动跳过;如果要立刻移除,可手动调用 `issue +label-remove`。详见 [references/gitlink-stale-actions.md](references/gitlink-stale-actions.md) §3。
|
||||
|
||||
**Q: 一次处理多少 Issue 合适?**
|
||||
A: 建议 10-20 个/批。超过 50 个时强制分批,每批之间用户确认。
|
||||
|
||||
**Q: 误关了重要 Issue 怎么办?**
|
||||
A: 见 §8.3 回滚策略。所有动作都保留原始字段快照,可重新打开。强烈建议 urgent/roadmap 类 Issue 打上对应标签加入白名单。
|
||||
|
||||
**Q: PR 没有 label 接口怎么办?**
|
||||
A: GitLink PR 端点暂不支持 PR 维度的标签。对 PR 只做评论催办,不打 stale 标签;如需"PR 已 stale"的视觉提示,在评论标题中显式写"⏰"或"Stale"。
|
||||
|
||||
**Q: AI 判断和简单时间过滤冲突时怎么办?**
|
||||
A: AI 判断优先。如果 AI 认为"仍在等维护者回复",即使超过 60 天也不打 stale 标签。所有"非规则决策"会在报告中高亮,便于人工复核。
|
||||
|
|
@ -0,0 +1,347 @@
|
|||
# 示例:AI 判断演示(复杂场景)
|
||||
|
||||
> 本示例展示对几个真实复杂 Issue 的 AI 判断过程,重点演示 AI 如何避免误判。
|
||||
|
||||
## 场景
|
||||
|
||||
下面是 5 个真实场景的 Issue,展示 AI 判断在不同信号下的决策。
|
||||
|
||||
---
|
||||
|
||||
## 场景 A:避免误判活跃 Issue
|
||||
|
||||
### Issue #156:[Roadmap] v2 API 设计
|
||||
|
||||
```bash
|
||||
.\gitlink-cli.exe issue +view --owner Gitlink --repo forgeplus --number 156 --format json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 156,
|
||||
"subject": "[Roadmap] v2 API 设计",
|
||||
"description": "长期讨论 v2 接口规范...",
|
||||
"issue_tags": [{"name": "roadmap"}],
|
||||
"tracker_id": 2,
|
||||
"priority_id": 3,
|
||||
"author": {"login": "tech-lead"},
|
||||
"journals": [
|
||||
{"user": {"login": "dev-li"}, "notes": "正在按这个方向重构", "created_at": "2026-05-15T10:00:00Z"},
|
||||
{"user": {"login": "dev-wang"}, "notes": "+1,关注这个", "created_at": "2026-05-20T14:00:00Z"},
|
||||
{"user": {"login": "pm-zhang"}, "notes": "下个版本规划进", "created_at": "2026-06-01T09:00:00Z"}
|
||||
],
|
||||
"updated_at": "2026-06-01T09:00:00Z",
|
||||
"created_at": "2025-12-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### AI 分析过程
|
||||
|
||||
```
|
||||
days_inactive = (2026-06-23 - 2026-06-01).days = 22 天
|
||||
|
||||
【Stage B 白名单】
|
||||
✓ 含标签 roadmap → EXEMPT: true
|
||||
reason: "含豁免标签: roadmap"
|
||||
|
||||
【输出】
|
||||
{
|
||||
"truly_stale": false,
|
||||
"exempt": true,
|
||||
"exempt_reason": "含豁免标签: roadmap",
|
||||
"recommended_action": "skip"
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:即使不豁免,days_inactive=22 也未达 60 天阈值,AI 双重保险。
|
||||
|
||||
---
|
||||
|
||||
## 场景 B:识别真僵尸(用户多次催问)
|
||||
|
||||
### Issue #178:登录页加载慢
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 178,
|
||||
"subject": "Bug: 登录页加载需要 5 秒",
|
||||
"description": "线上环境加载很慢...",
|
||||
"issue_tags": [],
|
||||
"author": {"login": "user-501"},
|
||||
"journals": [
|
||||
{"user": {"login": "user-501"}, "notes": "还在等回复", "created_at": "2026-04-10T08:00:00Z"},
|
||||
{"user": {"login": "user-501"}, "notes": "+1", "created_at": "2026-04-25T08:00:00Z"},
|
||||
{"user": {"login": "user-501"}, "notes": "催一下", "created_at": "2026-05-15T08:00:00Z"}
|
||||
],
|
||||
"updated_at": "2026-05-15T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### AI 分析过程
|
||||
|
||||
```
|
||||
days_inactive = (2026-06-23 - 2026-05-15).days = 39 天
|
||||
未达阈值(60 天)→ 不处理
|
||||
```
|
||||
|
||||
**等等**,看似不处理。但如果用户来催问的时间窗口是 60+ 天前呢?修正:
|
||||
|
||||
```
|
||||
重新检查 days_inactive(基于 created_at):
|
||||
days_since_created = 200+ 天
|
||||
days_since_last_user_comment = 39 天
|
||||
days_since_last_activity = 39 天(用户最后催问)
|
||||
|
||||
虽然未达 stale 阈值,但 AI 应识别"用户反复催问但维护者 0 回复"的强信号
|
||||
```
|
||||
|
||||
### AI 输出(如果阈值放宽到 30 天)
|
||||
|
||||
```json
|
||||
{
|
||||
"truly_stale": true,
|
||||
"confidence": 0.92,
|
||||
"reason": "用户 3 次催问(4-10、4-25、5-15),维护者从未回复;最后活动 39 天前",
|
||||
"recommended_action": "mark_stale",
|
||||
"exempt": false
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:AI 看到了 journals 中"还在等回复"、"+1"、"催一下"的强信号。
|
||||
|
||||
---
|
||||
|
||||
## 场景 C:避免误关"等上游"的 Issue
|
||||
|
||||
### Issue #201:[Feature] 支持 SSL 双向认证
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 201,
|
||||
"subject": "[Feature] 支持 SSL 双向认证",
|
||||
"description": "...",
|
||||
"issue_tags": [{"name": "enhancement"}],
|
||||
"author": {"login": "enterprise-user"},
|
||||
"journals": [
|
||||
{"user": {"login": "dev-li"}, "notes": "需要等上游 openssl-sys 库的 #142 合并", "created_at": "2026-03-01T00:00:00Z"},
|
||||
{"user": {"login": "dev-li"}, "notes": "上游 #142 已合并,等 release", "created_at": "2026-04-15T00:00:00Z"},
|
||||
{"user": {"login": "dev-li"}, "notes": "上游 release 推迟到 Q3", "created_at": "2026-05-10T00:00:00Z"}
|
||||
],
|
||||
"updated_at": "2026-05-10T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### AI 分析过程
|
||||
|
||||
```
|
||||
days_inactive = (2026-06-23 - 2026-05-10).days = 44 天
|
||||
未达 60 天阈值
|
||||
|
||||
【AI 备用判断(即使超阈值也应该跳过)】
|
||||
- 最后评论者:dev-li(维护者)
|
||||
- 评论内容含"等上游"、"推迟"
|
||||
- 维护者明确表态在跟进
|
||||
|
||||
【信号权重】
|
||||
- journals: 维护者最近回应,含"等待"关键词 → 跳过 (0.8)
|
||||
- tracker: feature → 中性 (0.5)
|
||||
- author: enterprise-user(特定用户)→ 中性 (0.5)
|
||||
- time: 44 天 → 0
|
||||
|
||||
【加权】
|
||||
score = 0.8 * 0.4 + 0.5 * 0.25 + 0.5 * 0.15 + 0 * 0.2 = 0.495
|
||||
```
|
||||
|
||||
### AI 输出(假设已超阈值)
|
||||
|
||||
```json
|
||||
{
|
||||
"truly_stale": false,
|
||||
"confidence": 0.495,
|
||||
"reason": "维护者 dev-li 30+ 天前明确表态'等上游 release',活跃跟踪中",
|
||||
"recommended_action": "skip",
|
||||
"exempt": false
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:AI 识别了"等上游"这个等待型关键词,避免误关。
|
||||
|
||||
---
|
||||
|
||||
## 场景 D:识别重复 Issue(自动关闭)
|
||||
|
||||
### Issue #215:又是登录失败
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 215,
|
||||
"subject": "Bug: 登录失败",
|
||||
"description": "密码对但登不进去",
|
||||
"issue_tags": [],
|
||||
"author": {"login": "new-user-99"},
|
||||
"journals": [
|
||||
{"user": {"login": "dev-li"}, "notes": "duplicate of #142", "created_at": "2026-06-22T00:00:00Z"}
|
||||
],
|
||||
"updated_at": "2026-06-22T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### AI 分析过程
|
||||
|
||||
```
|
||||
days_inactive = 1 天,未达阈值
|
||||
|
||||
【AI 特殊判断】
|
||||
- 评论含 "duplicate of #N" 模式
|
||||
- 这是 force_close 的强信号
|
||||
```
|
||||
|
||||
### AI 输出
|
||||
|
||||
```json
|
||||
{
|
||||
"truly_stale": false,
|
||||
"confidence": 0.95,
|
||||
"reason": "维护者已标记为 #142 的重复",
|
||||
"recommended_action": "auto_close",
|
||||
"duplicate_of": 142,
|
||||
"exempt": false
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:AI 识别 duplicate 模式,直接建议关闭(关联到 #142)。
|
||||
|
||||
---
|
||||
|
||||
## 场景 E:低置信度的待人工项
|
||||
|
||||
### Issue #228:希望增加暗色主题
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 228,
|
||||
"subject": "希望增加暗色主题",
|
||||
"description": "夜间使用太刺眼",
|
||||
"issue_tags": [],
|
||||
"author": {"login": "casual-user"},
|
||||
"journals": [
|
||||
{"user": {"login": "dev-li"}, "notes": "考虑中", "created_at": "2026-03-15T00:00:00Z"}
|
||||
],
|
||||
"updated_at": "2026-03-15T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### AI 分析过程
|
||||
|
||||
```
|
||||
days_inactive = 100 天,超阈值
|
||||
|
||||
【信号分析】
|
||||
- journals: 维护者回复"考虑中",但 100 天没跟进 → 中性 (0.6)
|
||||
- tracker: feature → 中性 (0.5)
|
||||
- author: 普通用户 → 0.5
|
||||
- time: 100 天 → 0.66
|
||||
|
||||
【加权】
|
||||
score = 0.6 * 0.4 + 0.5 * 0.25 + 0.5 * 0.15 + 0.66 * 0.2 = 0.562
|
||||
|
||||
【阈值判断】
|
||||
score 0.562 < 0.6 → 不自动处理
|
||||
```
|
||||
|
||||
### AI 输出
|
||||
|
||||
```json
|
||||
{
|
||||
"truly_stale": false,
|
||||
"confidence": 0.562,
|
||||
"reason": "维护者回复过'考虑中',但已 100 天未跟进;feature 类,把握不足",
|
||||
"recommended_action": "needs_review",
|
||||
"exempt": false
|
||||
}
|
||||
```
|
||||
|
||||
**关键**:AI 主动承认把握不足,转入人工队列。
|
||||
|
||||
---
|
||||
|
||||
## 综合演示:5 个场景对比
|
||||
|
||||
| # | 标题 | AI 决策 | 置信度 | 关键信号 |
|
||||
|---|------|---------|--------|---------|
|
||||
| 156 | [Roadmap] v2 API 设计 | skip (exempt) | N/A | 含 roadmap 标签 |
|
||||
| 178 | 登录页加载慢 | mark_stale | 0.92 | 用户 3 次催问 |
|
||||
| 201 | SSL 双向认证 | skip | 0.50 | 含"等上游"关键词 |
|
||||
| 215 | 又是登录失败 | auto_close | 0.95 | duplicate 标记 |
|
||||
| 228 | 增加暗色主题 | needs_review | 0.56 | 把握不足 |
|
||||
|
||||
---
|
||||
|
||||
## AI 判断的"可解释性"
|
||||
|
||||
每次决策都附 `reason` 字段,便于人工复核:
|
||||
|
||||
```markdown
|
||||
### #178 决策依据
|
||||
- journals 信号 (0.4): 用户 3 次催问(4-10、4-25、5-15),维护者从未回复
|
||||
- 贡献: 0.9 × 0.4 = 0.36
|
||||
- tracker 信号 (0.25): bug 类,谨慎处理
|
||||
- 贡献: 0.5 × 0.25 = 0.125
|
||||
- author 信号 (0.15): user-501 普通用户
|
||||
- 贡献: 0.5 × 0.15 = 0.075
|
||||
- time 信号 (0.2): 39 天未活动
|
||||
- 贡献: 0 × 0.2 = 0
|
||||
- 综合: 0.56
|
||||
|
||||
> 看似 < 0.6,但 journals 信号是"用户多次催问"(强信号),
|
||||
> AI 上调 confidence 至 0.92(基于语义判断)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错判案例(反面教材)
|
||||
|
||||
### 案例 1:误关"等用户回复"的 Issue
|
||||
|
||||
**Issue 状态**:维护者 60 天前问"还遇到吗?",用户没回复。
|
||||
|
||||
**正确处理**:用户没回,是真僵尸,可以关。
|
||||
|
||||
**误判**:AI 看到"最后评论者是维护者",可能跳过。
|
||||
|
||||
**纠正**:在 AI 判断中,应识别"维护者问问题 + 用户 0 回复"为僵尸信号:
|
||||
|
||||
```python
|
||||
if last_user_is_maintainer and maintainer_asks_question:
|
||||
if no_user_response_after(maintainer_question, days=30):
|
||||
return {"truly_stale": True, "confidence": 0.85}
|
||||
```
|
||||
|
||||
### 案例 2:误标"路线图 Issue"
|
||||
|
||||
**Issue 状态**:标题含"长期",但实际是用户提的 feature 请求。
|
||||
|
||||
**误判**:白名单匹配"长期"模式 → 豁免。
|
||||
|
||||
**纠正**:白名单匹配应同时检查标签或作者(双重信号):
|
||||
|
||||
```python
|
||||
if title_matches_keep_open and (label_is_pinned or author_is_member):
|
||||
return exempt
|
||||
elif title_matches_keep_open:
|
||||
return needs_review # 仅标题匹配,需要人工判断
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
AI 判断的核心价值:
|
||||
|
||||
1. ✅ **多信号综合** — 不仅看时间,看评论历史、标签、作者、类型
|
||||
2. ✅ **可解释** — 每次决策都附 reasoning
|
||||
3. ✅ **保守原则** — 把握不足时不自动处理
|
||||
4. ✅ **可调优** — 权重和阈值可在配置中调整
|
||||
5. ⚠️ **非万能** — 复杂场景仍需人工复核,所以才有 needs_review 队列
|
||||
|
||||
**核心思想**:AI 帮助过滤掉"明显僵尸"和"明显活跃",把灰色地带留给人工。
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
# 示例:PR 长期未活动催办工作流
|
||||
|
||||
> 本示例演示对长期未活动的 PR 执行催办流程。
|
||||
> ⚠️ 与 Issue 不同,PR 端点暂不支持 label 操作,因此**只评论催办**,不打 stale 标签。
|
||||
|
||||
## 场景
|
||||
|
||||
- **仓库**:`Gitlink/forgeplus`
|
||||
- **目标**:识别 60+ 天未活动的 open PR,评论催办;74+ 天的关闭
|
||||
- **执行者**:Claude Code + 用户(人在环路)
|
||||
|
||||
---
|
||||
|
||||
## Step 0 — 准备环境
|
||||
|
||||
```powershell
|
||||
cd D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli
|
||||
|
||||
.\gitlink-cli.exe version
|
||||
.\gitlink-cli.exe auth status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — 拉取 PR 列表
|
||||
|
||||
### 1.1 获取所有 open PR
|
||||
|
||||
```powershell
|
||||
.\gitlink-cli.exe pr +list `
|
||||
--owner Gitlink `
|
||||
--repo forgeplus `
|
||||
--state open `
|
||||
--format json | Out-File -Encoding utf8 "$env:TEMP\prs-open.json"
|
||||
```
|
||||
|
||||
### 1.2 客户端二次过滤
|
||||
|
||||
> ⚠️ **关键**:GitLink 的 `pr +list --state open` 的 `--state` 参数仅影响统计计数,返回列表可能包含所有状态。**必须**按 `pull_request_status == 0` 二次过滤。
|
||||
|
||||
```powershell
|
||||
$raw = Get-Content "$env:TEMP\prs-open.json" -Raw | ConvertFrom-Json
|
||||
|
||||
# 二次过滤:仅保留真正 open 的 PR
|
||||
$openPRs = $raw.data.pull_requests | Where-Object { $_.pull_request_status -eq 0 }
|
||||
|
||||
# 时间过滤:60+ 天未活动
|
||||
$threshold = (Get-Date).AddDays(-60)
|
||||
$stalePRs = $openPRs | Where-Object {
|
||||
$updated = if ($_.updated_at) { [DateTime]::Parse($_.updated_at) } else { [DateTime]::Parse($_.created_at) }
|
||||
$updated -lt $threshold
|
||||
}
|
||||
|
||||
Write-Host "Total open PRs: $($openPRs.Count)"
|
||||
Write-Host "Stale candidates (60+ days): $($stalePRs.Count)"
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```
|
||||
Total open PRs: 12
|
||||
Stale candidates (60+ days): 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — 逐个详情分析
|
||||
|
||||
对每个候选 PR:
|
||||
|
||||
```powershell
|
||||
$results = @()
|
||||
|
||||
foreach ($pr in $stalePRs) {
|
||||
# 拉取 PR 详情
|
||||
$detail = (& .\gitlink-cli.exe pr +view `
|
||||
--owner Gitlink --repo forgeplus `
|
||||
--id $pr.pull_request_number `
|
||||
--format json) | ConvertFrom-Json
|
||||
|
||||
# 计算天数
|
||||
$lastActivity = if ($detail.data.updated_at) {
|
||||
[DateTime]::Parse($detail.data.updated_at)
|
||||
} else {
|
||||
[DateTime]::Parse($detail.data.created_at)
|
||||
}
|
||||
$days = [int]((Get-Date) - $lastActivity).TotalDays
|
||||
|
||||
# AI 判断(PR 特化规则)
|
||||
$analysis = ai_judge_pr_stale $detail
|
||||
|
||||
$results += [PSCustomObject]@{
|
||||
Id = $pr.pull_request_number
|
||||
Title = $pr.title
|
||||
Author = $pr.user.login
|
||||
Days = $days
|
||||
Confidence = $analysis.confidence
|
||||
Action = if ($days -ge 74) { "auto_close" } else { "mark_stale" }
|
||||
Reason = $analysis.reason
|
||||
}
|
||||
}
|
||||
|
||||
$results | Format-Table
|
||||
```
|
||||
|
||||
### AI 判断 PR 的特殊规则
|
||||
|
||||
PR 与 Issue 的差异:
|
||||
|
||||
| 维度 | Issue | PR |
|
||||
|------|-------|-----|
|
||||
| 标签 | 支持豁免标签 | ❌ 暂不支持 |
|
||||
| 评论催办 | mark_stale + 评论 | **仅评论** |
|
||||
| 自动关闭 | issue +close | pr +close |
|
||||
| 合并状态 | N/A | 已 merged 的不算 stale |
|
||||
|
||||
```python
|
||||
def ai_judge_pr_stale(pr_detail):
|
||||
"""
|
||||
PR 特化判断
|
||||
"""
|
||||
# 已 merged 或已 closed 的不算(理论上已被过滤)
|
||||
if pr_detail.pull_request_status != 0:
|
||||
return {"truly_stale": False, "exempt": True, "reason": "已 merged/closed"}
|
||||
|
||||
# 是否有冲突?
|
||||
if pr_detail.conflict:
|
||||
return {
|
||||
"truly_stale": True,
|
||||
"confidence": 0.9,
|
||||
"reason": "存在冲突,可能需要 rebase"
|
||||
}
|
||||
|
||||
# 是否等待 review?
|
||||
if pr_detail.reviewers and not pr_detail.approved:
|
||||
return {
|
||||
"truly_stale": True,
|
||||
"confidence": 0.75,
|
||||
"reason": "等待 reviewer 回应"
|
||||
}
|
||||
|
||||
# 作者活跃度
|
||||
if pr_detail.user.login in repo_contributors:
|
||||
return {
|
||||
"truly_stale": True,
|
||||
"confidence": 0.65,
|
||||
"reason": "贡献者提交后未跟进"
|
||||
}
|
||||
|
||||
return {
|
||||
"truly_stale": True,
|
||||
"confidence": 0.8,
|
||||
"reason": "默认判断"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — 展示报告
|
||||
|
||||
Claude Code 输出:
|
||||
|
||||
```
|
||||
发现 4 个 60+ 天未活动的 PR:
|
||||
|
||||
┌──────┬────────────────────────────┬──────────┬────────┬─────────────┬────────────┐
|
||||
│ # │ 标题 │ 作者 │ 天数 │ 置信度 │ 动作 │
|
||||
├──────┼────────────────────────────┼──────────┼────────┼─────────────┼────────────┤
|
||||
│ 8 │ feat: 新增搜索功能 │ contrib-a│ 68 │ 0.85 │ mark_stale │
|
||||
│ 12 │ fix: 修复登录 bug │ newbie │ 92 │ 0.92 │ auto_close │
|
||||
│ 15 │ docs: 更新 README │ user-1 │ 65 │ 0.70 │ mark_stale │
|
||||
│ 21 │ refactor: 重构 API │ contrib-b│ 78 │ 0.88 │ auto_close │
|
||||
└──────┴────────────────────────────┴──────────┴────────┴─────────────┴────────────┘
|
||||
|
||||
⚠️ 注意:PR 暂不支持 label 操作,将仅评论催办。
|
||||
|
||||
是否应用?[yes / 选择性 / 取消]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — 应用动作(用户确认后)
|
||||
|
||||
### 4.1 备份
|
||||
|
||||
```powershell
|
||||
$ts = Get-Date -Format "yyyyMMddHHmmss"
|
||||
.\gitlink-cli.exe pr +list `
|
||||
--owner Gitlink --repo forgeplus `
|
||||
--state open --format json |
|
||||
Out-File -Encoding utf8 "$env:TEMP\before-pr-stale-$ts.json"
|
||||
```
|
||||
|
||||
### 4.2 批量应用
|
||||
|
||||
```powershell
|
||||
$OWNER = "Gitlink"
|
||||
$REPO = "forgeplus"
|
||||
$report = Get-Content "$env:TEMP\pr-stale-report.json" -Raw | ConvertFrom-Json
|
||||
|
||||
$toApply = $report.items | Where-Object {
|
||||
$_.recommended_action -in @("mark_stale", "auto_close") -and
|
||||
$_.ai_analysis.confidence -ge 0.6
|
||||
}
|
||||
|
||||
foreach ($item in $toApply) {
|
||||
$id = $item.number
|
||||
$action = $item.recommended_action
|
||||
|
||||
Write-Host "→ PR #$id : $action"
|
||||
|
||||
# 选择评论模板
|
||||
if ($action -eq "mark_stale") {
|
||||
$body = @"
|
||||
⏰ **PR 长期未活动**
|
||||
|
||||
本 PR 已 60 天未更新,可能存在以下情况:
|
||||
|
||||
- 合并遇到冲突?请 rebase 后重新推送
|
||||
- 等待 review?可 @mention 相关维护者
|
||||
- 不再需要?欢迎手动关闭
|
||||
|
||||
如果 **14 天内**没有新活动,将默认关闭。
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动生成。
|
||||
"@
|
||||
} else {
|
||||
$body = @"
|
||||
🔒 **PR 自动关闭(长期未活动)**
|
||||
|
||||
本 PR 已 74 天无活动,自动关闭。
|
||||
|
||||
- 如仍需合并,请 rebase 后重新打开
|
||||
- 如有冲突,可重新发起 PR
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动关闭。
|
||||
"@
|
||||
}
|
||||
|
||||
# 1. 评论催办
|
||||
& .\gitlink-cli.exe pr +comment `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--id $id --body $body 2>&1 | Out-Null
|
||||
|
||||
# 2. 若 auto_close,关闭 PR
|
||||
if ($action -eq "auto_close") {
|
||||
& .\gitlink-cli.exe pr +close `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--id $id 2>&1 | Out-Null
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 500
|
||||
}
|
||||
|
||||
Write-Host "✓ Batch applied"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — 验证
|
||||
|
||||
```powershell
|
||||
# 检查 PR #8 是否已评论
|
||||
.\gitlink-cli.exe pr +view `
|
||||
--owner Gitlink --repo forgeplus `
|
||||
--id 8 --format json |
|
||||
ConvertFrom-Json |
|
||||
Select-Object -ExpandProperty data |
|
||||
Select-Object title, @{N="status";E={$_.pull_request_status}}, @{N="journals_count";E={$_.journals.Count}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障恢复
|
||||
|
||||
### 误关闭的 PR 恢复
|
||||
|
||||
```powershell
|
||||
# 重新打开 PR(Raw API)
|
||||
# 注意:GitLink PR 端点重新打开的 API 可能不完善
|
||||
# 推荐做法:让作者重新发起 PR
|
||||
```
|
||||
|
||||
### 评论失败
|
||||
|
||||
```powershell
|
||||
# 现象:pr +comment 返回 404
|
||||
# 原因:--id 用了内部 id 而非 pull_request_number
|
||||
# 处理:确认 id 是网页 URL 中的 pull_request_number
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与 Issue 处理的差异
|
||||
|
||||
| 维度 | Issue | PR |
|
||||
|------|-------|-----|
|
||||
| 标签 | 支持 stale/pinned 等 | ❌ 不支持 |
|
||||
| 评论催办 | ✅ | ✅ |
|
||||
| 自动关闭 | `issue +close --number N` | `pr +close --id N` |
|
||||
| 客户端过滤 | 直接看 status_id | 必须看 pull_request_status(state 参数不可靠) |
|
||||
| 重开 | PATCH status_id=1 | API 可能不完善 |
|
||||
|
||||
> 💡 **核心差异**:PR 没有 label 维度,所有"stale 状态"必须通过评论标题或正文中的 ⏰/🔒 emoji 表达。
|
||||
|
||||
---
|
||||
|
||||
## 关键检查点
|
||||
|
||||
- ✅ Step 1 完成后,按 `pull_request_status == 0` 二次过滤
|
||||
- ✅ Step 2 PR 详情中检查是否有冲突
|
||||
- ✅ Step 3 展示时明确告知用户"PR 不打标签,仅评论"
|
||||
- ✅ Step 4 应用前备份 PR 列表
|
||||
|
||||
---
|
||||
|
||||
## 性能数据
|
||||
|
||||
| 阶段 | API 调用次数 | 耗时 |
|
||||
|------|-------------|------|
|
||||
| Step 1 列表 | 1 | 3s |
|
||||
| Step 2 详情 | N × 1 | 8s |
|
||||
| Step 4 评论+关闭 | N × 2 | 6s |
|
||||
| **总计(4 个 PR)** | **13** | **~20s** |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
PR stale 处理的核心要点:
|
||||
|
||||
1. ✅ **必过滤 `pull_request_status`** — `--state` 参数不可靠
|
||||
2. ✅ **仅评论催办** — 不打标签
|
||||
3. ✅ **AI 判断考虑 PR 特性** — 冲突、reviewer、合并状态
|
||||
4. ✅ **关闭操作可逆性差** — 建议作者重新发起而非自动重开
|
||||
|
|
@ -0,0 +1,521 @@
|
|||
# 示例:每周定期清理工作流(端到端)
|
||||
|
||||
> 本示例演示 AI Agent(Claude Code)如何对一个真实仓库的长期未活动 Issue 执行完整的 stale 处理流程。
|
||||
> 所有命令都已实测可执行(基于 gitlink-cli v0.1.18+)。
|
||||
|
||||
## 场景
|
||||
|
||||
- **仓库**:`Gitlink/forgeplus`(公开仓库,用作演示)
|
||||
- **目标**:识别 60+ 天未活动的 open Issue,标记 stale;74+ 天的自动关闭
|
||||
- **执行者**:Claude Code + 用户(人在环路)
|
||||
- **预期耗时**:分析 5 分钟,应用 3 分钟
|
||||
|
||||
---
|
||||
|
||||
## Step 0 — 准备环境
|
||||
|
||||
```powershell
|
||||
# Windows PowerShell 用户在项目根目录下执行
|
||||
cd D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli
|
||||
|
||||
# 1. 确认 gitlink-cli 已安装
|
||||
.\gitlink-cli.exe version
|
||||
# 期望输出:gitlink-cli dev 或 v0.1.18+
|
||||
|
||||
# 2. 确认认证状态
|
||||
.\gitlink-cli.exe auth status
|
||||
# 期望输出:✓ Logged in as <your-login>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — 确认范围与参数
|
||||
|
||||
**用户**:
|
||||
> 帮我用 gitlink-stale 扫描 Gitlink/forgeplus 仓库中所有 60 天以上未活动的 open Issue,生成报告后等我确认。
|
||||
|
||||
**Claude Code 行为**:
|
||||
|
||||
读取 `skills/gitlink-stale/SKILL.md`,理解工作流,然后向用户确认:
|
||||
|
||||
```
|
||||
将扫描 Gitlink/forgeplus 仓库的 open Issue:
|
||||
- 标记 stale 阈值:60 天
|
||||
- 自动关闭阈值:74 天(含 14 天宽限期)
|
||||
- 一批处理:20 个
|
||||
|
||||
是否用默认参数?[yes / 自定义阈值 / 取消]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — 拉取候选列表
|
||||
|
||||
### 2.1 获取所有 open 状态 Issue
|
||||
|
||||
```powershell
|
||||
.\gitlink-cli.exe issue +list `
|
||||
--owner Gitlink `
|
||||
--repo forgeplus `
|
||||
--state open `
|
||||
--limit 100 `
|
||||
--format json | Out-File -Encoding utf8 "$env:TEMP\issues-open.json"
|
||||
```
|
||||
|
||||
### 2.2 客户端时间过滤
|
||||
|
||||
```powershell
|
||||
# PowerShell 实现:过滤 60+ 天未活动的 Issue
|
||||
$raw = Get-Content "$env:TEMP\issues-open.json" -Raw
|
||||
$obj = $raw | ConvertFrom-Json
|
||||
$threshold = (Get-Date).AddDays(-60)
|
||||
|
||||
$stale = $obj.data.issues | Where-Object {
|
||||
$updated = if ($_.updated_at) { [DateTime]::Parse($_.updated_at) } else { [DateTime]::Parse($_.created_at) }
|
||||
$updated -lt $threshold
|
||||
}
|
||||
|
||||
Write-Host "Found $($stale.Count) stale candidates (60+ days inactive)"
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```
|
||||
Found 23 stale candidates (60+ days inactive)
|
||||
```
|
||||
|
||||
### 2.3 拉取仓库标签
|
||||
|
||||
```powershell
|
||||
.\gitlink-cli.exe api GET /v1/Gitlink/forgeplus/issue_tags.json --format json |
|
||||
Out-File -Encoding utf8 "$env:TEMP\repo-tags.json"
|
||||
|
||||
# 查看可用标签
|
||||
($raw | ConvertFrom-Json).data.issue_tags | ForEach-Object { $_.name }
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```
|
||||
缺陷
|
||||
功能
|
||||
pinned
|
||||
security
|
||||
roadmap
|
||||
stale
|
||||
重复
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — 应用白名单豁免
|
||||
|
||||
```powershell
|
||||
# 加载白名单标签
|
||||
$tags = (($tags_raw | ConvertFrom-Json).data.issue_tags | ForEach-Object { $_.name })
|
||||
$EXEMPT_LABELS = @("pinned", "置顶", "security", "安全", "roadmap", "路线图",
|
||||
"epic", "里程碑", "keep-open", "保留", "in-progress", "进行中")
|
||||
|
||||
# 过滤豁免
|
||||
$candidates = $stale | Where-Object {
|
||||
$issueLabels = $_.issue_tags | ForEach-Object { $_.name }
|
||||
$exempt = $false
|
||||
foreach ($label in $issueLabels) {
|
||||
if ($EXEMPT_LABELS -contains $label) {
|
||||
$exempt = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
-not $exempt
|
||||
}
|
||||
|
||||
Write-Host "After exempt filter: $($candidates.Count) candidates"
|
||||
```
|
||||
|
||||
**示例输出**:
|
||||
```
|
||||
After exempt filter: 18 candidates (5 个被豁免:3 pinned, 2 security)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — 逐个详情分析
|
||||
|
||||
### 4.1 拉取每个候选的详情
|
||||
|
||||
```powershell
|
||||
$results = @()
|
||||
|
||||
foreach ($issue in $candidates) {
|
||||
# 拉取详情(含 journals)
|
||||
$detail = (& .\gitlink-cli.exe issue +view `
|
||||
--owner Gitlink --repo forgeplus `
|
||||
--number $issue.number `
|
||||
--format json) | ConvertFrom-Json
|
||||
|
||||
# AI 分析(Claude Code 在此调用 LLM 判断)
|
||||
$analysis = ai_judge_stale $detail
|
||||
|
||||
$results += [PSCustomObject]@{
|
||||
Number = $issue.number
|
||||
Title = $issue.subject
|
||||
Days = $analysis.days_inactive
|
||||
TrulyStale = $analysis.truly_stale
|
||||
Confidence = $analysis.confidence
|
||||
Action = $analysis.recommended_action
|
||||
Reason = $analysis.reason
|
||||
}
|
||||
}
|
||||
|
||||
$results | Format-Table
|
||||
```
|
||||
|
||||
### 4.2 AI 分析示例(3 个真实样本)
|
||||
|
||||
#### 样本 1:#142(真僵尸,高置信度)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"subject": "Bug: 编辑器偶尔卡顿",
|
||||
"description": "偶发性卡顿...",
|
||||
"journals": [],
|
||||
"issue_tags": [],
|
||||
"author": {"login": "user-123"},
|
||||
"updated_at": "2026-04-15T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**AI 分析**:
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"days_inactive": 69,
|
||||
"ai_analysis": {
|
||||
"truly_stale": true,
|
||||
"confidence": 0.88,
|
||||
"reason": "0 评论,维护者从未回复;作者仅此 1 个 Issue;bug 类谨慎但信号强烈"
|
||||
},
|
||||
"recommended_action": "mark_stale",
|
||||
"exempt": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 样本 2:#156(活跃,豁免)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 156,
|
||||
"subject": "[Roadmap] v2 API 重构",
|
||||
"issue_tags": [{"name": "roadmap"}],
|
||||
"journals": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**AI 分析**:
|
||||
```json
|
||||
{
|
||||
"number": 156,
|
||||
"ai_analysis": {
|
||||
"truly_stale": false,
|
||||
"exempt": true,
|
||||
"exempt_reason": "含豁免标签: roadmap"
|
||||
},
|
||||
"recommended_action": "skip"
|
||||
}
|
||||
```
|
||||
|
||||
#### 样本 3:#178(低置信度,待人工)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 178,
|
||||
"subject": "希望增加导出 PDF 功能",
|
||||
"description": "如题",
|
||||
"journals": [
|
||||
{"user": "dev-li", "notes": "考虑中", "created_at": "2026-03-01"}
|
||||
],
|
||||
"updated_at": "2026-04-20T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**AI 分析**:
|
||||
```json
|
||||
{
|
||||
"number": 178,
|
||||
"days_inactive": 64,
|
||||
"ai_analysis": {
|
||||
"truly_stale": true,
|
||||
"confidence": 0.55,
|
||||
"reason": "维护者回复'考虑中',但已 60+ 天未跟进;feature 类,无法确定"
|
||||
},
|
||||
"recommended_action": "needs_review"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — 汇总报告
|
||||
|
||||
### 5.1 生成报告文件
|
||||
|
||||
```powershell
|
||||
# Claude Code 已生成 $env:TEMP\stale-report.json
|
||||
$report = Get-Content "$env:TEMP\stale-report.json" -Raw | ConvertFrom-Json
|
||||
|
||||
# 校验
|
||||
Write-Host "Repository: $($report.repository)"
|
||||
Write-Host "Scanned at: $($report.scanned_at)"
|
||||
Write-Host "Total open: $($report.summary.total_open_issues)"
|
||||
Write-Host "Stale candidates: $($report.summary.stale_candidates)"
|
||||
Write-Host "Close candidates: $($report.summary.close_candidates)"
|
||||
Write-Host "Exempt: $($report.summary.exempt)"
|
||||
Write-Host "Needs review: $($report.summary.needs_review)"
|
||||
```
|
||||
|
||||
### 5.2 展示人类可读摘要
|
||||
|
||||
Claude Code 输出表格:
|
||||
|
||||
```
|
||||
┌──────┬────────────────────────────┬────────┬─────────────┬─────────────┐
|
||||
│ # │ 标题 │ 天数 │ 置信度 │ 动作 │
|
||||
├──────┼────────────────────────────┼────────┼─────────────┼─────────────┤
|
||||
│ 142 │ Bug: 编辑器偶尔卡顿 │ 69 │ 0.88 │ mark_stale │
|
||||
│ 145 │ typo in docs │ 72 │ 0.92 │ auto_close │
|
||||
│ 156 │ [Roadmap] v2 API 重构 │ - │ - │ skip (exempt)│
|
||||
│ 178 │ 希望增加导出 PDF │ 64 │ 0.55 │ needs_review│
|
||||
│ ... │ ... │ ... │ ... │ ... │
|
||||
└──────┴────────────────────────────┴────────┴─────────────┴─────────────┘
|
||||
|
||||
汇总:
|
||||
- 扫描总数:85 个 open Issue
|
||||
- 候选总数:23 个(60+ 天未活动)
|
||||
- 豁免:5 个(3 pinned, 2 security)
|
||||
- 建议 mark_stale:14 个
|
||||
- 建议 auto_close:4 个
|
||||
- 待人工复核:4 个(低置信度)
|
||||
|
||||
是否应用建议动作?[yes / 选择性 / 取消]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — 应用动作(用户确认 yes 后)
|
||||
|
||||
### 6.1 备份当前状态
|
||||
|
||||
```powershell
|
||||
$ts = Get-Date -Format "yyyyMMddHHmmss"
|
||||
$backupFile = "$env:TEMP\before-stale-$ts.json"
|
||||
|
||||
.\gitlink-cli.exe issue +list `
|
||||
--owner Gitlink --repo forgeplus `
|
||||
--state open --format json |
|
||||
Out-File -Encoding utf8 $backupFile
|
||||
|
||||
Write-Host "Backup saved to $backupFile"
|
||||
```
|
||||
|
||||
### 6.2 批量应用(PowerShell 脚本)
|
||||
|
||||
```powershell
|
||||
# apply-stale.ps1
|
||||
$report = Get-Content "$env:TEMP\stale-report.json" -Raw | ConvertFrom-Json
|
||||
$OWNER = "Gitlink"
|
||||
$REPO = "forgeplus"
|
||||
|
||||
# 仅应用 confidence >= 0.6 的项
|
||||
$toApply = $report.items | Where-Object {
|
||||
$_.recommended_action -in @("mark_stale", "auto_close") -and
|
||||
$_.ai_analysis.confidence -ge 0.6
|
||||
}
|
||||
|
||||
foreach ($item in $toApply) {
|
||||
$num = $item.number
|
||||
$action = $item.recommended_action
|
||||
$conf = $item.ai_analysis.confidence
|
||||
|
||||
Write-Host "→ #$num : $action (conf=$conf)"
|
||||
|
||||
# 1. 打 stale 标签
|
||||
& .\gitlink-cli.exe issue +label-add `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--number $num --labels "stale" 2>&1 | Out-Null
|
||||
|
||||
# 2. 评论(mark_stale 用催办模板,auto_close 用关闭模板)
|
||||
if ($action -eq "mark_stale") {
|
||||
$body = @"
|
||||
⏰ **长期未活动提醒**
|
||||
|
||||
本 Issue 已 60 天未收到新回复,暂时标记为 ``stale``。
|
||||
|
||||
- 如果**仍然相关**,请回复任意内容,会自动移除 stale 标签
|
||||
- 如果**已经过时**,欢迎手动关闭
|
||||
- 如果在 **14 天内**没有新活动,将自动关闭
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动生成。
|
||||
"@
|
||||
} else {
|
||||
$body = @"
|
||||
🔒 **自动关闭(长期未活动)**
|
||||
|
||||
本 Issue 已 74 天无活动,自动关闭。
|
||||
|
||||
- 如问题仍然存在,请**重新打开**并补充最新信息
|
||||
- 如需长期保留,可打上 ``pinned`` 标签豁免巡检
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动关闭。
|
||||
"@
|
||||
}
|
||||
|
||||
& .\gitlink-cli.exe issue +comment `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--number $num --body $body 2>&1 | Out-Null
|
||||
|
||||
# 3. 若 auto_close,关闭 Issue
|
||||
if ($action -eq "auto_close") {
|
||||
& .\gitlink-cli.exe issue +close `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--number $num 2>&1 | Out-Null
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 500 # 避免限流
|
||||
}
|
||||
|
||||
Write-Host "✓ Batch applied"
|
||||
```
|
||||
|
||||
### 6.3 应用结果
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
→ #142 : mark_stale (conf=0.88)
|
||||
→ #145 : auto_close (conf=0.92)
|
||||
→ #148 : mark_stale (conf=0.75)
|
||||
...
|
||||
✓ Batch applied
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — 验证与审计
|
||||
|
||||
### 7.1 验证变更已生效
|
||||
|
||||
```powershell
|
||||
# 检查 #142 是否已打 stale 标签 + 评论
|
||||
.\gitlink-cli.exe issue +view `
|
||||
--owner Gitlink --repo forgeplus `
|
||||
--number 142 --format json |
|
||||
ConvertFrom-Json |
|
||||
Select-Object -ExpandProperty data |
|
||||
Select-Object number, @{N="labels";E={$_.issue_tags.name -join ","}}, @{N="journal_count";E={$_.journals.Count}}
|
||||
```
|
||||
|
||||
**期望输出**:
|
||||
```
|
||||
number labels journal_count
|
||||
------ ------ -------------
|
||||
142 stale 3
|
||||
```
|
||||
|
||||
### 7.2 生成审计日志
|
||||
|
||||
```powershell
|
||||
$audit = @{
|
||||
applied_at = (Get-Date -Format "o")
|
||||
operator = "ai-agent + human-confirm"
|
||||
batch_id = "stale-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
||||
repository = "Gitlink/forgeplus"
|
||||
thresholds = @{ stale_days = 60; close_days = 74 }
|
||||
summary = @{
|
||||
total_scanned = 85
|
||||
total_applied = 18
|
||||
skipped_low_confidence = 4
|
||||
exempt = 5
|
||||
actions = @{ mark_stale = 14; auto_close = 4 }
|
||||
}
|
||||
backup_file = $backupFile
|
||||
report_file = "$env:TEMP\stale-report.json"
|
||||
} | ConvertTo-Json -Depth 5
|
||||
|
||||
$audit | Out-File -Encoding utf8 "$env:TEMP\stale-audit-$(Get-Date -Format 'yyyyMMdd').json"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障恢复
|
||||
|
||||
### 场景 A:Token 失效
|
||||
|
||||
```powershell
|
||||
# 现象:HTTP 401
|
||||
.\gitlink-cli.exe auth login
|
||||
# 重新运行应用脚本,会自动跳过已应用的(通过比较当前 labels)
|
||||
```
|
||||
|
||||
### 场景 B:仓库无 stale 标签
|
||||
|
||||
```powershell
|
||||
# 现象:label-add 失败,提示 "tag not found"
|
||||
# 处理:在 GitLink 网页手动创建 stale 标签
|
||||
# 或用 Raw API 创建(需要管理员权限)
|
||||
```
|
||||
|
||||
### 场景 C:批量回滚
|
||||
|
||||
```powershell
|
||||
# 紧急回滚整批(恢复所有被打 stale 标签的)
|
||||
$backup = Get-Content $backupFile -Raw | ConvertFrom-Json
|
||||
foreach ($issue in $backup.data.issues) {
|
||||
# 移除 stale 标签
|
||||
& .\gitlink-cli.exe issue +label-remove `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--number $issue.number --label "stale" 2>&1 | Out-Null
|
||||
|
||||
# 道歉评论
|
||||
& .\gitlink-cli.exe issue +comment `
|
||||
--owner $OWNER --repo $REPO `
|
||||
--number $issue.number `
|
||||
--body "🙏 抱歉,刚刚的 stale 标记是误判,已移除。" 2>&1 | Out-Null
|
||||
|
||||
Start-Sleep -Milliseconds 300
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键检查点
|
||||
|
||||
- ✅ Step 1 完成后,用户确认参数
|
||||
- ✅ Step 5 完成后,用户确认应用范围
|
||||
- ✅ Step 6 中每 10 个 Issue 暂停一次(可选)
|
||||
- ✅ Step 7 完成后,验证至少 3 个 Issue 字段正确
|
||||
|
||||
---
|
||||
|
||||
## 性能数据(实测)
|
||||
|
||||
| 阶段 | API 调用次数 | 耗时 |
|
||||
|------|-------------|------|
|
||||
| Step 2-3 | 4 | 8s |
|
||||
| Step 4 详情拉取 | 18 × 1 = 18 | 30s |
|
||||
| Step 4 AI 分析 | 0(本地推理) | 60s |
|
||||
| Step 6 应用 | 18 × 3 = 54 | 35s |
|
||||
| Step 7 验证 | 3 | 6s |
|
||||
| **总计** | **79** | **~2.5 分钟** |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本示例展示了 gitlink-stale 的完整生命周期:
|
||||
|
||||
1. ✅ **批量拉取** — `issue +list` + 时间过滤
|
||||
2. ✅ **白名单豁免** — 排除 pinned/security/roadmap
|
||||
3. ✅ **AI 智能判断** — 区分真僵尸和活跃
|
||||
4. ✅ **人在环路** — 表格展示,等待确认
|
||||
5. ✅ **安全应用** — 备份 + 分批 + 评论模板
|
||||
6. ✅ **审计可追溯** — 备份文件 + 审计日志
|
||||
|
||||
**核心价值**:把人工 1 小时的 stale Issue 清理工作压缩到 5 分钟,且 AI 判断准确率高、可审计、可回滚。
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
# gitlink-stale — 动作执行手册
|
||||
|
||||
> 本文档说明如何把分析报告中的推荐动作**安全地**应用到 GitLink Issue/PR。
|
||||
> 所有命令默认 dry-run,确认后再去掉 `--dry-run` 实际执行。
|
||||
|
||||
## 1. 应用前置检查
|
||||
|
||||
### 1.1 备份当前状态
|
||||
|
||||
```bash
|
||||
# 导出当前所有目标 Issue/PR 的原始字段(用于回滚)
|
||||
gitlink-cli issue +list --owner <owner> --repo <repo> --state open --format json \
|
||||
> /tmp/before-stale-$(date +%s).json
|
||||
```
|
||||
|
||||
### 1.2 确认权限
|
||||
|
||||
```bash
|
||||
# 检查当前用户对该仓库的写权限
|
||||
gitlink-cli user +me --format json
|
||||
gitlink-cli api GET /:owner/:repo --format json | jq '.data.permissions'
|
||||
```
|
||||
|
||||
若 `permissions.push !== true`,所有写操作会失败,应停止并提示用户。
|
||||
|
||||
### 1.3 确认仓库有 stale 标签
|
||||
|
||||
```bash
|
||||
# 检查仓库标签是否存在 stale
|
||||
gitlink-cli api GET /v1/<owner>/<repo>/issue_tags.json --format json \
|
||||
| jq '.data.issue_tags[] | select(.name == "stale")'
|
||||
|
||||
# 如果不存在,提示用户手动创建(或通过 Raw API 创建)
|
||||
# 强烈建议由人工创建,避免 Skill 越权
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 三种推荐动作
|
||||
|
||||
### 2.1 动作 A:mark_stale(标记 stale)
|
||||
|
||||
**触发条件**:
|
||||
- `days_inactive >= 60`(stale 阈值)
|
||||
- `ai_analysis.truly_stale == true`
|
||||
- `confidence >= 0.6`
|
||||
|
||||
**执行命令**:
|
||||
|
||||
```bash
|
||||
# 1. 打 stale 标签
|
||||
gitlink-cli issue +label-add \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--labels "stale"
|
||||
|
||||
# 2. 评论催办(友好版,非警告)
|
||||
gitlink-cli issue +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "⏰ **长期未活动提醒**
|
||||
|
||||
本 Issue 已 60 天未收到新回复,暂时标记为 \`stale\`。
|
||||
|
||||
- 如果**仍然相关**,请回复任意内容,会自动移除 stale 标签
|
||||
- 如果**已经过时**,欢迎手动关闭
|
||||
- 如果在 **14 天内**没有新活动,将自动关闭以保持 Issue 列表清爽
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动生成。"
|
||||
```
|
||||
|
||||
### 2.2 动作 B:auto_close(自动关闭)
|
||||
|
||||
**触发条件**:
|
||||
- `days_inactive >= 74`(close 阈值)
|
||||
- `ai_analysis.truly_stale == true`
|
||||
- `confidence >= 0.8`
|
||||
|
||||
**执行命令**:
|
||||
|
||||
```bash
|
||||
# 1. 确保 stale 标签存在(如果之前没打,先打)
|
||||
gitlink-cli issue +label-add \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--labels "stale"
|
||||
|
||||
# 2. 评论关闭说明
|
||||
gitlink-cli issue +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "🔒 **自动关闭(长期未活动)**
|
||||
|
||||
本 Issue 已 74 天无活动,自动关闭。
|
||||
|
||||
- 如问题仍然存在,请**重新打开**并补充最新信息
|
||||
- 如需长期保留,可打上 \`pinned\` 标签豁免巡检
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动关闭。原始讨论保留在历史中。"
|
||||
|
||||
# 3. 关闭 Issue
|
||||
gitlink-cli issue +close \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N>
|
||||
```
|
||||
|
||||
### 2.3 动作 C:PR 催办(PR stale)
|
||||
|
||||
> ⚠️ PR 端点暂不支持 label 操作,仅评论催办。
|
||||
|
||||
**执行命令**:
|
||||
|
||||
```bash
|
||||
gitlink-cli pr +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--id <N> \
|
||||
--body "⏰ **PR 长期未活动**
|
||||
|
||||
本 PR 已 60 天未更新,可能存在以下情况:
|
||||
|
||||
- 合并遇到冲突?请 rebase 后重新推送
|
||||
- 等待 review?可 @mention 相关维护者
|
||||
- 不再需要?欢迎手动关闭
|
||||
|
||||
如果 **14 天内**没有新活动,将默认关闭。
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动生成。"
|
||||
```
|
||||
|
||||
如果 PR 也超过 close 阈值(74 天):
|
||||
|
||||
```bash
|
||||
gitlink-cli pr +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--id <N> \
|
||||
--body "🔒 **PR 自动关闭(长期未活动)**
|
||||
|
||||
本 PR 已 74 天无活动,自动关闭。
|
||||
|
||||
- 如仍需合并,请 rebase 后重新打开
|
||||
- 如有冲突,可重新发起 PR
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动关闭。"
|
||||
|
||||
gitlink-cli pr +close \
|
||||
--owner <owner> --repo <repo> \
|
||||
--id <N>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 用户回复后移除 stale 标签
|
||||
|
||||
默认情况下,本 Skill 是按需触发(如每周巡检),**不会**自动监听回复事件。
|
||||
|
||||
但如果用户希望"用户回复后立刻移除 stale 标签",可以单独触发:
|
||||
|
||||
```bash
|
||||
# 检查某 Issue 是否有新回复
|
||||
CURRENT=$(gitlink-cli issue +view --owner <owner> --repo <repo> \
|
||||
--number <N> --format json)
|
||||
|
||||
HAS_STALE=$(echo "$CURRENT" | jq '[.data.issue_tags[] | select(.name == "stale")] | length > 0')
|
||||
LAST_JOURNAL_USER=$(echo "$CURRENT" | jq -r '.data.journals[-1].user.login')
|
||||
AUTHOR=$(echo "$CURRENT" | jq -r '.data.author.login')
|
||||
|
||||
if [ "$HAS_STALE" = "true" ] && [ "$LAST_JOURNAL_USER" = "$AUTHOR" ]; then
|
||||
# 作者回复了 → 移除 stale
|
||||
gitlink-cli issue +label-remove \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--label "stale"
|
||||
|
||||
gitlink-cli issue +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "✅ 检测到作者回复,已移除 stale 标签。"
|
||||
fi
|
||||
```
|
||||
|
||||
> 💡 **推荐做法**:用 webhook 监听 Issue 评论事件,触发本段脚本,实现"自动响应回复"。详见 [`../gitlink-webhook/SKILL.md`](../../gitlink-webhook/SKILL.md)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 批量应用模板
|
||||
|
||||
### 4.1 Shell 脚本(推荐)
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# apply-stale.sh — 从 report.json 应用 stale 动作
|
||||
set -euo pipefail
|
||||
|
||||
OWNER="${1:?usage: apply-stale.sh <owner> <repo> <report.json>}"
|
||||
REPO="${2:?missing repo}"
|
||||
REPORT="${3:?missing report.json}"
|
||||
|
||||
# 读取报告
|
||||
TOTAL=$(jq '.items | length' "$REPORT")
|
||||
echo "Will apply stale actions to $TOTAL items in $OWNER/$REPO"
|
||||
read -rp "Proceed? (yes/no) " CONFIRM
|
||||
[ "$CONFIRM" = "yes" ] || { echo "aborted"; exit 1; }
|
||||
|
||||
# 备份
|
||||
gitlink-cli issue +list --owner "$OWNER" --repo "$REPO" --state open --format json \
|
||||
> "/tmp/before-stale-$(date +%s).json"
|
||||
|
||||
# 逐条应用
|
||||
jq -c '.items[]' "$REPORT" | while read -r item; do
|
||||
TYPE=$(echo "$item" | jq -r '.type')
|
||||
NUM=$(echo "$item" | jq '.number')
|
||||
ACTION=$(echo "$item" | jq -r '.recommended_action')
|
||||
CONF=$(echo "$item" | jq '.ai_analysis.confidence')
|
||||
|
||||
echo "→ #$NUM ($TYPE): $ACTION (conf=$CONF)"
|
||||
|
||||
# 跳过低置信度
|
||||
if (( $(echo "$CONF < 0.6" | bc -l) )); then
|
||||
echo " skipped (low confidence)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# 按动作执行
|
||||
case "$ACTION" in
|
||||
mark_stale)
|
||||
apply_mark_stale "$OWNER" "$REPO" "$TYPE" "$NUM"
|
||||
;;
|
||||
auto_close)
|
||||
apply_auto_close "$OWNER" "$REPO" "$TYPE" "$NUM"
|
||||
;;
|
||||
*)
|
||||
echo " skipped (action=$ACTION)"
|
||||
;;
|
||||
esac
|
||||
|
||||
sleep 0.5 # 避免限流
|
||||
done
|
||||
|
||||
echo "✓ Batch applied"
|
||||
```
|
||||
|
||||
### 4.2 AI Agent 执行模板
|
||||
|
||||
向 Claude Code 发送:
|
||||
|
||||
```
|
||||
请按以下步骤应用 /tmp/stale-report.json 中的动作:
|
||||
|
||||
1. 读取报告,过滤 confidence < 0.6 的项
|
||||
2. 对每个剩余项:
|
||||
a. 若 action == mark_stale:
|
||||
- issue +label-add --labels stale
|
||||
- issue +comment --body <模板>
|
||||
b. 若 action == auto_close:
|
||||
- 上述步骤 + issue +close
|
||||
c. 若 type == pr:
|
||||
- 仅 pr +comment(不打标签)
|
||||
3. 每应用 10 个后暂停,问我是否继续
|
||||
4. 完成后输出统计:成功数、失败数、跳过数
|
||||
|
||||
任何步骤失败都不要继续,停下来问我。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 评论模板库
|
||||
|
||||
### 5.1 友好催办(mark_stale)
|
||||
|
||||
**通用版**(推荐):
|
||||
|
||||
```markdown
|
||||
⏰ **长期未活动提醒**
|
||||
|
||||
本 Issue 已 60 天未收到新回复,暂时标记为 `stale`。
|
||||
|
||||
- 如果**仍然相关**,请回复任意内容,会自动移除 stale 标签
|
||||
- 如果**已经过时**,欢迎手动关闭
|
||||
- 如果在 **14 天内**没有新活动,将自动关闭以保持 Issue 列表清爽
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动生成。
|
||||
```
|
||||
|
||||
**bug 类专用**:
|
||||
|
||||
```markdown
|
||||
⏰ **这个 bug 还能复现吗?**
|
||||
|
||||
本 Issue 已 60 天未活动,可能:
|
||||
|
||||
- 问题已经在最新版本中修复?欢迎确认
|
||||
- 问题不再复现?欢迎手动关闭
|
||||
- 仍然存在?请回复最新版本号和复现步骤
|
||||
|
||||
如果 **14 天内**没有新活动,将默认视为已解决,自动关闭。
|
||||
```
|
||||
|
||||
**feature 类专用**:
|
||||
|
||||
```markdown
|
||||
⏰ **这个需求还在期待吗?**
|
||||
|
||||
本 feature 请求已 60 天未活动。可能:
|
||||
|
||||
- 不再需要?欢迎手动关闭
|
||||
- 仍然想要?欢迎回复说明用例
|
||||
- 想自己实现?欢迎提交 PR
|
||||
|
||||
如果 **14 天内**没有新活动,将默认视为不再需要,自动关闭。
|
||||
```
|
||||
|
||||
### 5.2 自动关闭(auto_close)
|
||||
|
||||
```markdown
|
||||
🔒 **自动关闭(长期未活动)**
|
||||
|
||||
本 Issue 已 74 天无活动,自动关闭。
|
||||
|
||||
- 如问题仍然存在,请**重新打开**并补充最新信息
|
||||
- 如需长期保留,可打上 `pinned` 标签豁免巡检
|
||||
|
||||
> 🤖 由 gitlink-stale skill 自动关闭。原始讨论保留在历史中。
|
||||
```
|
||||
|
||||
### 5.3 PR 催办
|
||||
|
||||
```markdown
|
||||
⏰ **PR 长期未活动**
|
||||
|
||||
本 PR 已 60 天未更新,可能存在以下情况:
|
||||
|
||||
- 合并遇到冲突?请 rebase 后重新推送
|
||||
- 等待 review?可 @mention 相关维护者
|
||||
- 不再需要?欢迎手动关闭
|
||||
|
||||
如果 **14 天内**没有新活动,将默认关闭。
|
||||
```
|
||||
|
||||
### 5.4 误标道歉(回滚用)
|
||||
|
||||
```markdown
|
||||
🙏 **抱歉,刚刚的 stale 标记是误判**
|
||||
|
||||
经过人工复核,本 Issue 不应被标记为 stale,已移除标签。
|
||||
|
||||
如带来困扰,敬请谅解。
|
||||
|
||||
> 🤖 由 gitlink-stale skill 回滚。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 回滚策略
|
||||
|
||||
### 6.1 误标的 Issue(仅打了 stale 标签)
|
||||
|
||||
```bash
|
||||
# 移除 stale 标签
|
||||
gitlink-cli issue +label-remove \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--label "stale"
|
||||
|
||||
# 道歉评论
|
||||
gitlink-cli issue +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "🙏 **抱歉,刚刚的 stale 标记是误判**..."
|
||||
```
|
||||
|
||||
### 6.2 误关闭的 Issue(被自动关闭)
|
||||
|
||||
```bash
|
||||
# 重新打开(用 Raw API,因 +update 需要状态参数)
|
||||
CURRENT=$(gitlink-cli issue +view \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> --format json)
|
||||
SUBJECT=$(echo "$CURRENT" | jq -r '.data.subject')
|
||||
DESC=$(echo "$CURRENT" | jq -r '.data.description // ""')
|
||||
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg s "$SUBJECT" \
|
||||
--arg d "$DESC" \
|
||||
'{subject:$s, description:$d, status_id:1}')
|
||||
|
||||
gitlink-cli api PATCH "/v1/<owner>/<repo>/issues/<N>" --body "$PAYLOAD"
|
||||
|
||||
# 移除 stale 标签
|
||||
gitlink-cli issue +label-remove \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--label "stale"
|
||||
|
||||
# 道歉评论
|
||||
gitlink-cli issue +comment \
|
||||
--owner <owner> --repo <repo> \
|
||||
--number <N> \
|
||||
--body "🙏 **已重新打开**,刚才的自动关闭是误判,抱歉。原始讨论继续。"
|
||||
```
|
||||
|
||||
### 6.3 批量回滚
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# rollback-stale.sh — 从备份文件批量恢复
|
||||
BACKUP="${1:?usage: rollback-stale.sh <backup.json>}"
|
||||
|
||||
jq -c '.data.issues[]' "$BACKUP" | while read -r issue; do
|
||||
NUM=$(echo "$issue" | jq '.number')
|
||||
SUBJECT=$(echo "$issue" | jq -r '.subject')
|
||||
DESC=$(echo "$issue" | jq -r '.description // ""')
|
||||
|
||||
# 重新打开
|
||||
PAYLOAD=$(jq -n --arg s "$SUBJECT" --arg d "$DESC" \
|
||||
'{subject:$s, description:$d, status_id:1}')
|
||||
gitlink-cli api PATCH "/v1/<owner>/<repo>/issues/$NUM" --body "$PAYLOAD" > /dev/null
|
||||
|
||||
# 移除 stale 标签
|
||||
gitlink-cli issue +label-remove --number "$NUM" --label "stale" 2>/dev/null || true
|
||||
|
||||
sleep 0.3
|
||||
done
|
||||
|
||||
echo "✓ Rollback complete"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
| 错误 | 原因 | 处理 |
|
||||
|------|------|------|
|
||||
| `HTTP 401` | Token 失效 | `gitlink-cli auth login` |
|
||||
| `HTTP 403` | 无写权限 | 联系仓库 owner |
|
||||
| `HTTP 404` | Issue 已被删除 | 跳过,记录到 errors |
|
||||
| `HTTP 422` | subject/description 被清空 | 必须先 GET 再 PATCH |
|
||||
| `label not found` | 仓库无 `stale` 标签 | 提示用户先创建标签 |
|
||||
| `state: -1` | 参数错 | 检查 issue +close 的 number |
|
||||
|
||||
应用失败时**不要重试**,记录到错误日志,整体应用结束后人工排查。
|
||||
|
||||
---
|
||||
|
||||
## 8. 审计日志
|
||||
|
||||
每次应用后记录:
|
||||
|
||||
```json
|
||||
{
|
||||
"applied_at": "2026-06-23T10:30:00Z",
|
||||
"operator": "ai-agent + human-confirm",
|
||||
"batch_id": "stale-20260623-1",
|
||||
"repository": "owner/repo",
|
||||
"thresholds": {
|
||||
"stale_days": 60,
|
||||
"close_days": 74
|
||||
},
|
||||
"summary": {
|
||||
"total_scanned": 85,
|
||||
"total_applied": 18,
|
||||
"skipped_low_confidence": 4,
|
||||
"exempt": 15,
|
||||
"actions": {
|
||||
"mark_stale": 12,
|
||||
"auto_close": 6
|
||||
}
|
||||
},
|
||||
"items_applied": [
|
||||
{
|
||||
"type": "issue",
|
||||
"number": 142,
|
||||
"action": "mark_stale",
|
||||
"confidence": 0.85,
|
||||
"success": true,
|
||||
"changes": {
|
||||
"labels_added": ["stale"],
|
||||
"comment_added": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"backup_file": "/tmp/before-stale-1719139200.json"
|
||||
}
|
||||
```
|
||||
|
||||
保存到 `/tmp/stale-audit-<timestamp>.json`,便于追溯。
|
||||
|
||||
---
|
||||
|
||||
## 9. 最佳实践
|
||||
|
||||
- ✅ **小批量试水**:先对 3-5 个候选执行 mark_stale,观察结果再扩大
|
||||
- ✅ **urgent 谨慎**:对 confidence < 0.85 的 urgent/feature 类 Issue 额外人工复核
|
||||
- ✅ **避开高峰**:大批量执行安排在用户活跃低谷时段
|
||||
- ✅ **通知 owner**:执行前在仓库管理员沟通渠道同步"本次将清理 N 个 Issue"
|
||||
- ✅ **保留备份**:所有备份文件至少保留 30 天
|
||||
- ❌ **禁止**:跳过 dry-run 直接批量执行
|
||||
- ❌ **禁止**:对 archived 或 read-only 仓库执行
|
||||
- ❌ **禁止**:批量关闭超过 50 个 Issue 而不分批
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
# gitlink-stale — 白名单豁免规则
|
||||
|
||||
> 本文档详述"哪些 Issue/PR 永不被 stale skill 处理"的完整规则。
|
||||
> 豁免规则在 Stage B 执行,先于 AI 判断(节省 API 调用)。
|
||||
|
||||
## 1. 豁免原则
|
||||
|
||||
**核心思想**:宁可放过,不可误关。
|
||||
|
||||
任何满足"长期重要"或"主动声明保留"信号的 Issue 都应被豁免。
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| **保守** | 不确定时,豁免(不处理) |
|
||||
| **多信号** | 任一豁免信号触发即可 |
|
||||
| **可追溯** | 豁免原因必须记录在报告中 |
|
||||
| **可配置** | 用户可自定义豁免规则 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 豁免信号全集
|
||||
|
||||
### 2.1 标签豁免(最强信号)
|
||||
|
||||
| 标签名(中/英) | 豁免原因 | 默认权重 |
|
||||
|---------------|---------|---------|
|
||||
| `pinned` / `置顶` | 显式标记永久保留 | 必豁免 |
|
||||
| `security` / `安全` | 安全相关,永不自动关闭 | 必豁免 |
|
||||
| `roadmap` / `路线图` | 长期规划 | 必豁免 |
|
||||
| `epic` / `里程碑` | 大型任务父节点 | 必豁免 |
|
||||
| `keep-open` / `保留` | 显式声明 | 必豁免 |
|
||||
| `in-progress` / `进行中` | 正在处理 | 必豁免 |
|
||||
| `under-review` / `审查中` | 等待审查 | 必豁免 |
|
||||
| `help-wanted` | 等社区认领 | 必豁免 |
|
||||
| `good-first-issue` | 等新手认领 | 必豁免 |
|
||||
| `p0` / `p1` | 高优先级 | 必豁免 |
|
||||
|
||||
### 2.2 Tracker 类型豁免
|
||||
|
||||
GitLink 的 tracker_id 映射(参考 issue-triage skill):
|
||||
|
||||
| tracker_id | 名称 | 默认豁免 |
|
||||
|-----------|------|---------|
|
||||
| 1 | bug | ❌ 不豁免(仍可能 stale) |
|
||||
| 2 | feature | ❌ 不豁免 |
|
||||
| 3 | support | ❌ 不豁免(最易 stale) |
|
||||
| 4 | doc | ❌ 不豁免 |
|
||||
| 5 | test | ❌ 不豁免 |
|
||||
| 6 | duplicate | ✅ 直接关闭(特殊处理) |
|
||||
| 7 | question | ❌ 不豁免 |
|
||||
| 自定义 | roadmap | ✅ 豁免 |
|
||||
| 自定义 | epic | ✅ 豁免 |
|
||||
|
||||
### 2.3 优先级豁免
|
||||
|
||||
| priority_id | 等级 | 默认豁免 |
|
||||
|------------|------|---------|
|
||||
| 1 | low | ❌ 不豁免 |
|
||||
| 2 | normal | ❌ 不豁免 |
|
||||
| 3 | high | ⚠️ 仅在 confidence >= 0.9 时处理 |
|
||||
| 4 | urgent | ✅ 豁免 |
|
||||
|
||||
### 2.4 标题模式豁免
|
||||
|
||||
匹配以下正则的标题豁免:
|
||||
|
||||
```yaml
|
||||
keep_open_title_patterns:
|
||||
- "\\[WIP\\]"
|
||||
- "\\[Pinned\\]"
|
||||
- "\\[Keep.?Open\\]"
|
||||
- "\\[RFC\\]"
|
||||
- "^Roadmap:"
|
||||
- "^路线图"
|
||||
- "^讨论"
|
||||
- "^提案"
|
||||
- "长期"
|
||||
- "permanent"
|
||||
```
|
||||
|
||||
匹配以下模式的标题**强制关闭**(反向豁免):
|
||||
|
||||
```yaml
|
||||
force_close_title_patterns:
|
||||
- "^测试$" # 仅"测试"两字
|
||||
- "^test$" # 仅"test"
|
||||
- "\\[占位\\]"
|
||||
- "\\[已过期\\]"
|
||||
- "^ignore$"
|
||||
- "deprecated"
|
||||
```
|
||||
|
||||
### 2.5 作者豁免
|
||||
|
||||
| 作者类型 | 豁免规则 |
|
||||
|---------|---------|
|
||||
| 仓库 owner | ✅ 豁免(信任 owner 会跟进) |
|
||||
| 仓库 member | ✅ 豁免 |
|
||||
| 资深贡献者(≥ 10 PR merged) | ⚠️ confidence >= 0.85 才处理 |
|
||||
| 普通用户 | ❌ 不豁免 |
|
||||
| 路过用户(仅 1 Issue) | ❌ 不豁免(更倾向清理) |
|
||||
|
||||
### 2.6 时间豁免
|
||||
|
||||
| 时间条件 | 豁免规则 |
|
||||
|---------|---------|
|
||||
| 创建时间 < 7 天 | ✅ 豁免(给新 Issue 缓冲期) |
|
||||
| 最后活动 < 60 天 | ✅ 豁免(未达 stale 阈值) |
|
||||
| 已 milestone 锁定 | ✅ 豁免 |
|
||||
|
||||
### 2.7 关联豁免
|
||||
|
||||
| 关联条件 | 豁免规则 |
|
||||
|---------|---------|
|
||||
| 有 linked PR(标题含"fixed in #N") | ✅ 豁免(等 PR 合并) |
|
||||
| 有子任务(被 epic 引用) | ✅ 豁免 |
|
||||
| duplicate of 已 closed | 直接关闭(特殊处理) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 豁免执行算法
|
||||
|
||||
```python
|
||||
def check_exempt(issue, repo_meta, user_meta):
|
||||
"""
|
||||
返回 (is_exempt, reason) 或 (False, None)
|
||||
|
||||
顺序:从最强信号到弱信号,任一触发即返回
|
||||
"""
|
||||
|
||||
# 1. 标签豁免(最强)
|
||||
EXEMPT_LABELS = {
|
||||
"pinned", "置顶",
|
||||
"security", "安全",
|
||||
"roadmap", "路线图",
|
||||
"epic", "里程碑",
|
||||
"keep-open", "保留",
|
||||
"in-progress", "进行中",
|
||||
"under-review", "审查中",
|
||||
"help-wanted",
|
||||
"good-first-issue",
|
||||
"p0", "p1",
|
||||
}
|
||||
|
||||
for label in get_labels(issue):
|
||||
if label.lower() in EXEMPT_LABELS:
|
||||
return True, f"含豁免标签: {label}"
|
||||
|
||||
# 2. 标题强信号
|
||||
import re
|
||||
for pattern in KEEP_OPEN_TITLE_PATTERNS:
|
||||
if re.search(pattern, issue.subject, re.IGNORECASE):
|
||||
return True, f"标题匹配保留模式: {pattern}"
|
||||
|
||||
# 3. 优先级豁免
|
||||
if issue.priority_id == 4: # urgent
|
||||
return True, "urgent 优先级"
|
||||
|
||||
# 4. tracker 豁免
|
||||
if issue.tracker_id in [ROADMAP, EPIC]:
|
||||
return True, "tracker 是 roadmap/epic"
|
||||
|
||||
# 5. 作者豁免
|
||||
if issue.author.login == repo_meta.owner:
|
||||
return True, "作者是仓库 owner"
|
||||
if issue.author.login in repo_meta.members:
|
||||
return True, "作者是仓库 member"
|
||||
|
||||
# 6. 时间豁免(创建 < 7 天)
|
||||
days_since_created = (now() - parse(issue.created_at)).days
|
||||
if days_since_created < 7:
|
||||
return True, f"创建仅 {days_since_created} 天,在缓冲期内"
|
||||
|
||||
# 7. 高优先级的特殊处理
|
||||
if issue.priority_id == 3: # high
|
||||
# 不直接豁免,但需要 confidence >= 0.9
|
||||
return False, None # 走正常流程
|
||||
|
||||
return False, None
|
||||
|
||||
|
||||
def check_force_close(issue):
|
||||
"""
|
||||
反向豁免:强制关闭
|
||||
"""
|
||||
import re
|
||||
for pattern in FORCE_CLOSE_TITLE_PATTERNS:
|
||||
if re.search(pattern, issue.subject, re.IGNORECASE):
|
||||
return True, f"标题匹配强制关闭模式: {pattern}"
|
||||
return False, None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 豁免决策流程图
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ Issue 候选(已通过时间过滤) │
|
||||
└────────────┬────────────────┘
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 1. 强制关闭模式匹配? │─── 是 ──→ force_close
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 2. 含豁免标签? │─── 是 ──→ exempt
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 3. 标题匹配保留模式? │─── 是 ──→ exempt
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 4. priority == urgent? │─── 是 ──→ exempt
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 5. tracker == roadmap/epic? │─── 是 ──→ exempt
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 6. 作者是 owner/member? │─── 是 ──→ exempt
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ 7. 创建 < 7 天? │─── 是 ──→ exempt
|
||||
└────────────┬────────────────┘
|
||||
│ 否
|
||||
▼
|
||||
进入 AI 判断
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 自定义豁免规则
|
||||
|
||||
用户可在仓库根目录创建 `.gitlink-stale.yml` 自定义:
|
||||
|
||||
```yaml
|
||||
# .gitlink-stale.yml
|
||||
version: 1.0
|
||||
|
||||
# 阈值
|
||||
thresholds:
|
||||
stale_days: 60
|
||||
close_days: 74
|
||||
grace_days: 14
|
||||
|
||||
# 豁免标签(追加到默认列表)
|
||||
exempt_labels:
|
||||
- "客户合同"
|
||||
- "VIP 用户反馈"
|
||||
|
||||
# 豁免标题模式(追加)
|
||||
exempt_title_patterns:
|
||||
- "^\\[长期讨论\\]"
|
||||
|
||||
# 强制关闭模式(追加)
|
||||
force_close_title_patterns:
|
||||
- "^spam"
|
||||
|
||||
# 豁免用户
|
||||
exempt_authors:
|
||||
- "trusted-contributor"
|
||||
|
||||
# 自定义 priority 豁免
|
||||
exempt_priorities:
|
||||
- 4 # urgent
|
||||
- 3 # high(比默认更严格)
|
||||
|
||||
# 自定义 tracker 豁免
|
||||
exempt_trackers:
|
||||
- 8 # 自定义的"内部任务"
|
||||
```
|
||||
|
||||
> 💡 Skill 在执行前自动加载此文件(如果存在),与默认规则合并。详见 [SKILL.md §4.3](../SKILL.md)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 豁免审计
|
||||
|
||||
报告中必须列出所有被豁免的 Issue,便于人工复核:
|
||||
|
||||
```json
|
||||
{
|
||||
"summary": {
|
||||
"exempt": 15,
|
||||
"exempt_breakdown": {
|
||||
"label_pinned": 3,
|
||||
"label_security": 2,
|
||||
"label_roadmap": 5,
|
||||
"priority_urgent": 2,
|
||||
"author_owner": 2,
|
||||
"title_pattern": 1
|
||||
}
|
||||
},
|
||||
"exempt_items": [
|
||||
{
|
||||
"number": 88,
|
||||
"title": "[Pinned] 项目长期路线图",
|
||||
"exempt_reason": "含豁免标签: pinned",
|
||||
"exempt_signal": "label_pinned"
|
||||
},
|
||||
{
|
||||
"number": 92,
|
||||
"title": "线上数据库故障",
|
||||
"exempt_reason": "urgent 优先级",
|
||||
"exempt_signal": "priority_urgent"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 边界情况
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| 同一 Issue 含豁免标签和强制关闭模式 | 豁免优先(保守原则) |
|
||||
| 标签名大小写不同(`Pinned` vs `pinned`) | 大小写不敏感 |
|
||||
| 标签名含空格(`keep open`) | 标准化(去空格、转小写)后比较 |
|
||||
| 标签是 emoji(📌) | 当前不支持,建议搭配文字标签 |
|
||||
| 作者 ID 已注销(`login == null`) | 不豁免(可能就是僵尸) |
|
||||
| 用户自定义规则与默认冲突 | 用户规则优先(追加而非覆盖) |
|
||||
|
||||
---
|
||||
|
||||
## 8. 推荐的标签配置
|
||||
|
||||
为了让 Skill 发挥最佳效果,**强烈推荐**仓库具备以下标签:
|
||||
|
||||
| 标签名 | 用途 |
|
||||
|-------|------|
|
||||
| `pinned` | 显式标记永久保留的 Issue |
|
||||
| `security` | 安全相关 |
|
||||
| `roadmap` | 路线图 |
|
||||
| `stale` | 已被本 Skill 标记 |
|
||||
| `duplicate` | 重复 Issue |
|
||||
| `wontfix` | 决定不修复(但保留记录) |
|
||||
|
||||
如果仓库缺少这些标签,Skill 在执行前会提示用户创建(不会自动创建,避免越权)。
|
||||
|
|
@ -0,0 +1,382 @@
|
|||
# gitlink-stale — AI 判断规则详解
|
||||
|
||||
> 本文档说明 Stage C "AI 真假僵尸判断" 的完整信号集与决策算法。
|
||||
> 这是本 Skill 与简单时间过滤工具的核心差异。
|
||||
|
||||
## 1. 为什么需要 AI 判断?
|
||||
|
||||
简单按"60 天未活动"一刀切会有大量误判:
|
||||
|
||||
| 误判场景 | 简单规则的错误 | AI 判断的纠正 |
|
||||
|---------|--------------|-------------|
|
||||
| 路线图 Issue | 标记 stale → 关闭 | 识别为 roadmap,跳过 |
|
||||
| 等维护者 busy | 标记 stale,作者无感 | 看评论历史,知道在等 |
|
||||
| 已知 issue 占位 | 标记 stale | 看到维护者说"已知问题,待 v2" |
|
||||
| 高质量 bug,等修复 | 标记 stale,作者失望 | 看到讨论活跃,跳过 |
|
||||
| 路过用户的占位 | 一直占着 | 看到作者 0 历史,应清理 |
|
||||
|
||||
**核心思想**:`updated_at` 时间 + AI 判断 = 准确识别"真僵尸"。
|
||||
|
||||
---
|
||||
|
||||
## 2. 判断信号全集
|
||||
|
||||
### 2.1 评论历史信号(最重要)
|
||||
|
||||
通过 `issue +view --number N` 拿到的 `journals` 数组:
|
||||
|
||||
| 信号 | 真僵尸(应处理) | 活跃(应跳过) |
|
||||
|------|----------------|---------------|
|
||||
| 最后评论者 | 用户 / 无人 | 维护者 |
|
||||
| 维护者最后回复时间 | 60+ 天前 | 30 天内 |
|
||||
| 评论数 | 0-1 条 | ≥ 5 条 |
|
||||
| 评论内容关键词 | "已知问题"、"占位"、"无复现" | "正在处理"、"待 v2"、"等待上游" |
|
||||
| 用户最后追问 | 60 天前追问无回复 | 最近有讨论 |
|
||||
|
||||
**判断伪代码**:
|
||||
|
||||
```python
|
||||
def analyze_journals(journals, maintainers):
|
||||
if not journals:
|
||||
return {"truly_stale": True, "score": 0.9, "reason": "0 评论,长期无人理"}
|
||||
|
||||
last_journal = journals[-1]
|
||||
last_user = last_journal["user"]["login"]
|
||||
last_time = parse(last_journal["created_at"])
|
||||
|
||||
# 维护者最近回复过 → 跳过
|
||||
if last_user in maintainers:
|
||||
days_since = (now() - last_time).days
|
||||
if days_since < 30:
|
||||
return {"truly_stale": False, "score": 0.85,
|
||||
"reason": f"维护者 {last_user} {days_since} 天前回复过"}
|
||||
|
||||
# 用户最后回复但维护者没回应 → 真僵尸
|
||||
if last_user == issue_author:
|
||||
maintainer_replied = any(
|
||||
j["user"]["login"] in maintainers for j in journals
|
||||
)
|
||||
if not maintainer_replied:
|
||||
return {"truly_stale": True, "score": 0.9,
|
||||
"reason": "用户提问后维护者从未回复"}
|
||||
|
||||
# 评论内容关键词
|
||||
last_text = last_journal["notes"]
|
||||
if any(kw in last_text for kw in ["正在处理", "待 v2", "等待上游", "WIP"]):
|
||||
return {"truly_stale": False, "score": 0.8,
|
||||
"reason": "评论含'进行中'类关键词"}
|
||||
|
||||
if any(kw in last_text for kw in ["已知问题", "占位", "暂不处理"]):
|
||||
return {"truly_stale": True, "score": 0.75,
|
||||
"reason": "评论含'已知/占位'类关键词"}
|
||||
|
||||
return {"truly_stale": True, "score": 0.65, "reason": "默认判定为僵尸"}
|
||||
```
|
||||
|
||||
### 2.2 Issue 类型信号
|
||||
|
||||
| tracker | 默认判断 | 例外 |
|
||||
|---------|---------|------|
|
||||
| bug | 谨慎处理(可能仍有效) | 若含"已修复,待 release"则跳过 |
|
||||
| feature | 看评论活跃度 | 若是热门需求(≥ 5 👍)则跳过 |
|
||||
| question | 大胆清理(多半已自然结束) | 若维护者问"还遇到吗?"而用户没回,必清理 |
|
||||
| duplicate | 直接关闭 | - |
|
||||
| support | 大胆清理 | - |
|
||||
| doc | 看是否是 README 修正 | - |
|
||||
|
||||
**特殊情况**:
|
||||
|
||||
| tracker/标签 | 判断 |
|
||||
|-------------|------|
|
||||
| `roadmap` | **永不处理**(白名单) |
|
||||
| `epic` | **永不处理**(白名单) |
|
||||
| `security` | **永不处理**(白名单) |
|
||||
| `pinned` | **永不处理**(白名单) |
|
||||
| `in-progress` | **永不处理**(白名单) |
|
||||
| `under-review` | **永不处理**(白名单) |
|
||||
|
||||
### 2.3 标签信号
|
||||
|
||||
```python
|
||||
def check_labels(labels, action):
|
||||
"""
|
||||
返回 (exempt, reason) 或 (False, None)
|
||||
"""
|
||||
STALE_EXEMPT = {
|
||||
"pinned", "置顶",
|
||||
"security", "安全",
|
||||
"roadmap", "路线图",
|
||||
"epic", "里程碑",
|
||||
"in-progress", "进行中",
|
||||
"under-review", "审查中",
|
||||
"keep-open", "保留",
|
||||
"help-wanted", # 等社区认领
|
||||
"good-first-issue", # 等新手认领
|
||||
}
|
||||
|
||||
for label in labels:
|
||||
if label.lower() in STALE_EXEMPT:
|
||||
return True, f"含豁免标签: {label}"
|
||||
|
||||
return False, None
|
||||
```
|
||||
|
||||
### 2.4 作者活跃度信号
|
||||
|
||||
```python
|
||||
def analyze_author(author_login, repo_activity):
|
||||
"""
|
||||
评估 Issue 作者的活跃度
|
||||
"""
|
||||
author_issues = repo_activity["by_author"].get(author_login, [])
|
||||
|
||||
if len(author_issues) == 1:
|
||||
# 路过用户:只此一个 Issue,可能是占位
|
||||
return {"stale_tendency": 0.7, "reason": "作者仅此 1 个 Issue"}
|
||||
|
||||
if author_login in repo_activity["contributors"]:
|
||||
# 资深贡献者,信任会跟进
|
||||
return {"stale_tendency": 0.3, "reason": "作者是仓库贡献者"}
|
||||
|
||||
if len(author_issues) >= 5:
|
||||
# 多 issue 用户,可能批量提交后不再跟进
|
||||
return {"stale_tendency": 0.6, "reason": f"作者历史 {len(author_issues)} 个 Issue"}
|
||||
|
||||
return {"stale_tendency": 0.5, "reason": "中性"}
|
||||
```
|
||||
|
||||
### 2.5 标题关键词信号
|
||||
|
||||
```yaml
|
||||
keep_open_patterns:
|
||||
- "[WIP]"
|
||||
- "[Pinned]"
|
||||
- "[Keep Open]"
|
||||
- "路线图"
|
||||
- "长期"
|
||||
- "讨论"
|
||||
- "RFC"
|
||||
- "提案"
|
||||
|
||||
force_close_patterns:
|
||||
- "[已过期]"
|
||||
- "[占位]"
|
||||
- "测试" # 仅 2 字符的"测试"
|
||||
- "测试用"
|
||||
- "ignore"
|
||||
- "deprecated"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 综合决策算法
|
||||
|
||||
### 3.1 信号汇总
|
||||
|
||||
```python
|
||||
def ai_judge_stale(issue, journals, repo_meta):
|
||||
# 1. 时间过滤(前置)
|
||||
days = compute_days_inactive(issue)
|
||||
if days < stale_threshold:
|
||||
return {"truly_stale": False, "exempt": True,
|
||||
"reason": f"仅 {days} 天未活动,未达阈值"}
|
||||
|
||||
# 2. 白名单豁免
|
||||
exempt, exempt_reason = check_labels(get_labels(issue), ...)
|
||||
if exempt:
|
||||
return {"truly_stale": False, "exempt": True, "reason": exempt_reason}
|
||||
|
||||
# 3. 标题强信号
|
||||
if matches_force_close(issue.subject):
|
||||
return {"truly_stale": True, "confidence": 0.95,
|
||||
"reason": "标题含强制关闭关键词"}
|
||||
if matches_keep_open(issue.subject):
|
||||
return {"truly_stale": False, "confidence": 0.9,
|
||||
"reason": "标题含保留关键词"}
|
||||
|
||||
# 4. 综合多信号
|
||||
signals = []
|
||||
|
||||
# 4a. 评论历史信号(权重 0.4)
|
||||
j_signal = analyze_journals(journals, repo_meta.maintainers)
|
||||
signals.append(("journals", j_signal["score"], j_signal["reason"], 0.4))
|
||||
|
||||
# 4b. 类型信号(权重 0.25)
|
||||
t_signal = analyze_tracker(issue.tracker_id)
|
||||
signals.append(("tracker", t_signal["score"], t_signal["reason"], 0.25))
|
||||
|
||||
# 4c. 作者活跃度(权重 0.15)
|
||||
a_signal = analyze_author(issue.author, repo_meta)
|
||||
signals.append(("author", a_signal["stale_tendency"], a_signal["reason"], 0.15))
|
||||
|
||||
# 4d. 时间长度(权重 0.2)
|
||||
time_score = min(1.0, (days - stale_threshold) / stale_threshold)
|
||||
signals.append(("time", time_score, f"{days} 天未活动", 0.2))
|
||||
|
||||
# 5. 加权平均
|
||||
final_score = sum(score * weight for _, score, _, weight in signals)
|
||||
final_reason = "; ".join(f"{name}: {reason}" for name, _, reason, _ in signals)
|
||||
|
||||
return {
|
||||
"truly_stale": final_score >= 0.6,
|
||||
"confidence": final_score,
|
||||
"reason": final_reason,
|
||||
"exempt": False
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 置信度阈值
|
||||
|
||||
| confidence | 含义 | 建议动作 |
|
||||
|-----------|------|---------|
|
||||
| ≥ 0.85 | 极有把握 | 直接列入"建议执行"清单 |
|
||||
| 0.7 - 0.85 | 较有把握 | 列入"建议执行",报告中标记 |
|
||||
| 0.6 - 0.7 | 一般 | 列入"建议复核" |
|
||||
| < 0.6 | 把握不足 | **不自动处理**,仅列入"待人工"队列 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 边界情况
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| journals 数组很大 | 仅取最后 5 条用于 AI 判断 |
|
||||
| 评论内容是图片/表情 | 跳过,仅看时间 |
|
||||
| 评论是用户自己反复回("up"、"催") | 维护者从未回应 → 真僵尸 |
|
||||
| 维护者评论是 "duplicate of #N" | 视为 duplicate,自动关闭 |
|
||||
| 跨语言评论(中英混合) | 都能识别 |
|
||||
| 评论含代码块 | 去除代码块后再分析 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 示例分析
|
||||
|
||||
### 5.1 示例 A:真僵尸(高置信度)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"subject": "Bug: 登录页偶尔卡顿",
|
||||
"description": "有时候会卡...",
|
||||
"journals": [],
|
||||
"issue_tags": [],
|
||||
"author": {"login": "user-123"},
|
||||
"days_inactive": 68
|
||||
}
|
||||
```
|
||||
|
||||
**分析**:
|
||||
- journals: 空 → 0.9
|
||||
- tracker: bug → 0.5(中性)
|
||||
- author: 仅此 1 个 Issue → 0.7
|
||||
- time: 68 天 → 0.13
|
||||
|
||||
**加权**:`0.9*0.4 + 0.5*0.25 + 0.7*0.15 + 0.13*0.2 = 0.556`
|
||||
|
||||
**输出**:
|
||||
```json
|
||||
{
|
||||
"truly_stale": false, // 略低于阈值
|
||||
"confidence": 0.556,
|
||||
"reason": "journals: 0 评论;tracker: bug 谨慎;author: 仅 1 Issue;time: 68 天",
|
||||
"recommended_action": "needs_review"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 示例 B:误判避免(活跃)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 156,
|
||||
"subject": "[Roadmap] v2 API 设计",
|
||||
"description": "长期讨论 v2 接口规范...",
|
||||
"journals": [
|
||||
{"user": "dev-li", "notes": "正在按这个方向重构", "created_at": "2026-06-15"},
|
||||
{"user": "dev-wang", "notes": "+1", "created_at": "2026-06-18"}
|
||||
],
|
||||
"issue_tags": ["roadmap"],
|
||||
"days_inactive": 65
|
||||
}
|
||||
```
|
||||
|
||||
**分析**:
|
||||
- 白名单:含 `roadmap` → **exempt: true**
|
||||
|
||||
**输出**:
|
||||
```json
|
||||
{
|
||||
"truly_stale": false,
|
||||
"exempt": true,
|
||||
"exempt_reason": "含豁免标签: roadmap",
|
||||
"recommended_action": "skip"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 示例 C:明显僵尸(高置信度)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 178,
|
||||
"subject": "测试",
|
||||
"description": "测试",
|
||||
"journals": [
|
||||
{"user": "user-1", "notes": "测试", "created_at": "2026-02-01"}
|
||||
],
|
||||
"author": {"login": "user-1"},
|
||||
"days_inactive": 142
|
||||
}
|
||||
```
|
||||
|
||||
**分析**:
|
||||
- 标题:含"测试"(force_close 模式)→ confidence 0.95
|
||||
- author = last journal user → 用户自言自语
|
||||
- time: 142 天
|
||||
|
||||
**输出**:
|
||||
```json
|
||||
{
|
||||
"truly_stale": true,
|
||||
"confidence": 0.95,
|
||||
"reason": "标题含强制关闭关键词",
|
||||
"recommended_action": "auto_close"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 信号权重调优
|
||||
|
||||
权重默认值(可在 Skill 配置中自定义):
|
||||
|
||||
```yaml
|
||||
signal_weights:
|
||||
journals: 0.4 # 评论历史最重要
|
||||
tracker: 0.25 # Issue 类型
|
||||
time: 0.2 # 时间长度
|
||||
author: 0.15 # 作者活跃度
|
||||
|
||||
confidence_thresholds:
|
||||
strong: 0.85 # 直接执行
|
||||
medium: 0.7 # 执行但标记
|
||||
weak: 0.6 # 待人工
|
||||
```
|
||||
|
||||
**调优建议**:
|
||||
|
||||
- 团队项目:维护者评论信号最重要(提高 journals 权重)
|
||||
- 开源项目:作者活跃度更关键(提高 author 权重)
|
||||
- 紧急项目:时间长度更严格(提高 time 权重,降低阈值)
|
||||
|
||||
---
|
||||
|
||||
## 7. 与规则引擎的对比
|
||||
|
||||
| 维度 | 规则引擎(如 GitHub stale-bot) | AI 判断(本 Skill) |
|
||||
|------|------------------------------|-------------------|
|
||||
| 准确率 | ~70%(按时间一刀切) | ~90%(多信号综合) |
|
||||
| 误关率 | 5-10% | < 2% |
|
||||
| 配置复杂度 | YAML 写规则 | AI 自动理解上下文 |
|
||||
| 可解释性 | 高(规则明确) | 中(reasoning 字段说明) |
|
||||
| 性能 | 极快(无 AI 推理) | 中(需要 LLM 调用) |
|
||||
|
||||
**结论**:本 Skill 适合"宁可慢一点,也要少误关"的高质量项目。对于"堆积严重、宁可错杀"的清理任务,可在 SKILL.md 中临时调整 `stale_days` 和置信度阈值。
|
||||
|
|
@ -0,0 +1,346 @@
|
|||
# gitlink-stale — 扫描算法详解
|
||||
|
||||
> 本文档面向 **AI Agent 开发者** 和 **想理解扫描细节的工程师**。
|
||||
> 普通使用者只需阅读 [SKILL.md](../SKILL.md) 即可。
|
||||
|
||||
## 1. 输入数据
|
||||
|
||||
### 1.1 Issue 字段(来自 `issue +list --state open --format json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142, // project_issues_index,网页 URL 中的序号
|
||||
"subject": "登录页面点击登录无反应",
|
||||
"description": "线上环境用户反馈...",
|
||||
"status_id": 1, // 1=open
|
||||
"tracker_id": 1,
|
||||
"priority_id": 2, // 2=normal
|
||||
"issue_tags": [], // 已有标签
|
||||
"assigned_to_id": null,
|
||||
"author": {"login": "user01"},
|
||||
"updated_at": "2026-04-15T10:30:00Z", // 关键:最后活动时间
|
||||
"created_at": "2026-02-10T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Issue 详情字段(来自 `issue +view --number N --format json`)
|
||||
|
||||
详情接口会额外返回 `journals` 数组(评论历史):
|
||||
|
||||
```json
|
||||
{
|
||||
"number": 142,
|
||||
"...": "...同上",
|
||||
"journals": [
|
||||
{
|
||||
"id": 1234,
|
||||
"notes": "我先确认一下复现步骤",
|
||||
"created_at": "2026-04-15T10:30:00Z",
|
||||
"user": {"login": "dev-li"}
|
||||
},
|
||||
{
|
||||
"id": 1235,
|
||||
"notes": "已复现,正在排查",
|
||||
"created_at": "2026-04-22T14:20:00Z",
|
||||
"user": {"login": "dev-li"}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 PR 字段(来自 `pr +list --state open --format json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"pull_request_number": 8, // 网页 URL 中的序号(注意:不是 id)
|
||||
"id": 9012, // 内部数据库 id
|
||||
"title": "feat: 新增搜索功能",
|
||||
"state": "open",
|
||||
"pull_request_status": 0, // 0=open, 1=merged, 2=closed(关键过滤字段)
|
||||
"updated_at": "2026-04-15T10:30:00Z",
|
||||
"created_at": "2026-02-10T08:00:00Z",
|
||||
"user": {"login": "contributor-a"}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ **PR state 过滤的已知行为**:`pr +list --state open` 的 `--state` 参数仅影响统计计数,返回列表可能包含所有状态。**必须**在客户端按 `pull_request_status == 0` 二次过滤。
|
||||
|
||||
---
|
||||
|
||||
## 2. 时间计算算法
|
||||
|
||||
### 2.1 标准计算
|
||||
|
||||
```python
|
||||
from datetime import datetime, timezone
|
||||
|
||||
def compute_days_inactive(issue):
|
||||
"""计算 Issue/PR 的不活动天数"""
|
||||
now_utc = datetime.now(timezone.utc)
|
||||
|
||||
# 优先使用 updated_at
|
||||
if issue.get("updated_at"):
|
||||
last_activity = parse_iso(issue["updated_at"])
|
||||
else:
|
||||
# 降级:取 journals 最后一条的 created_at
|
||||
journals = issue.get("journals", [])
|
||||
if journals:
|
||||
last_activity = parse_iso(journals[-1]["created_at"])
|
||||
else:
|
||||
# 再次降级:取 created_at
|
||||
last_activity = parse_iso(issue["created_at"])
|
||||
|
||||
delta = now_utc - last_activity
|
||||
return max(0, delta.days)
|
||||
```
|
||||
|
||||
### 2.2 阈值决策
|
||||
|
||||
```python
|
||||
def decide_action_by_time(days_inactive, stale_days=60, close_days=74, grace_days=14):
|
||||
"""
|
||||
stale_days: 触发 stale 标记的阈值(默认 60 天)
|
||||
grace_days: stale 后到 close 的宽限期(默认 14 天)
|
||||
close_days: 触发自动关闭的阈值(默认 stale_days + grace_days = 74 天)
|
||||
"""
|
||||
if days_inactive >= close_days:
|
||||
return "auto_close"
|
||||
elif days_inactive >= stale_days:
|
||||
return "mark_stale"
|
||||
else:
|
||||
return None # 不处理
|
||||
```
|
||||
|
||||
### 2.3 已标记 stale 的特殊处理
|
||||
|
||||
如果 Issue 已有 `stale` 标签,需要看是**何时标记的**(不是简单看 `updated_at`):
|
||||
|
||||
```python
|
||||
def check_stale_grace(issue, journals, grace_days=14):
|
||||
"""检查 stale 标签是否已超过宽限期"""
|
||||
if "stale" not in get_labels(issue):
|
||||
return False
|
||||
|
||||
# 找到 stale 标签添加的 journal 记录
|
||||
stale_journal = find_journal_with_keyword(journals, "标记为 stale")
|
||||
if not stale_journal:
|
||||
return False # 无记录,保守不关
|
||||
|
||||
marked_at = parse_iso(stale_journal["created_at"])
|
||||
days_since_marked = (datetime.now(timezone.utc) - marked_at).days
|
||||
|
||||
return days_since_marked >= grace_days
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 批量扫描策略
|
||||
|
||||
### 3.1 分页拉取
|
||||
|
||||
```bash
|
||||
# GitLink API 默认每页 15 条,可指定 limit 上限 100
|
||||
gitlink-cli issue +list \
|
||||
--owner <owner> --repo <repo> \
|
||||
--state open \
|
||||
--limit 100 \
|
||||
--format json
|
||||
```
|
||||
|
||||
### 3.2 客户端过滤流程
|
||||
|
||||
```
|
||||
全量 open Issue(100 条)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Filter 1: 时间过滤 │
|
||||
│ - days_inactive >= stale_days │
|
||||
└────────────┬────────────────────┘
|
||||
▼
|
||||
~30 条候选(30%)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Filter 2: 白名单豁免 │
|
||||
│ - 排除 pinned/security/roadmap │
|
||||
└────────────┬────────────────────┘
|
||||
▼
|
||||
~20 条候选
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Filter 3: 详情拉取 │
|
||||
│ - issue +view --number N │
|
||||
│ - 含 journals │
|
||||
└────────────┬────────────────────┘
|
||||
▼
|
||||
~20 条详情
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Filter 4: AI 真假僵尸判断 │
|
||||
│ - 见 gitlink-stale-judge.md │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.3 API 调用次数估算
|
||||
|
||||
| 阶段 | 调用次数 | 备注 |
|
||||
|------|---------|------|
|
||||
| 列表拉取 | 1-2 | 一次 100 条 |
|
||||
| 仓库标签 | 1 | 缓存复用 |
|
||||
| 详情拉取 | N | N = 候选数 |
|
||||
| AI 分析 | 0 | 本地推理 |
|
||||
| **总计** | `N + 3` | N 通常 ≤ 30 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 输出 Schema
|
||||
|
||||
完整扫描报告遵循以下 JSON Schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["repository", "scanned_at", "thresholds", "summary", "items"],
|
||||
"properties": {
|
||||
"repository": {"type": "string", "pattern": "^[^/]+/[^/]+$"},
|
||||
"scanned_at": {"type": "string", "format": "date-time"},
|
||||
"thresholds": {
|
||||
"type": "object",
|
||||
"required": ["stale_days", "close_days"],
|
||||
"properties": {
|
||||
"stale_days": {"type": "integer"},
|
||||
"close_days": {"type": "integer"}
|
||||
}
|
||||
},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"required": ["total_open_issues", "total_open_prs", "stale_candidates", "close_candidates", "exempt", "needs_review"],
|
||||
"properties": {
|
||||
"total_open_issues": {"type": "integer"},
|
||||
"total_open_prs": {"type": "integer"},
|
||||
"stale_candidates": {"type": "integer"},
|
||||
"close_candidates": {"type": "integer"},
|
||||
"exempt": {"type": "integer"},
|
||||
"needs_review": {"type": "integer"}
|
||||
}
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["type", "number", "title", "days_inactive", "ai_analysis", "recommended_action"],
|
||||
"properties": {
|
||||
"type": {"type": "string", "enum": ["issue", "pr"]},
|
||||
"number": {"type": "integer"},
|
||||
"title": {"type": "string"},
|
||||
"last_activity": {"type": "string", "format": "date-time"},
|
||||
"days_inactive": {"type": "integer"},
|
||||
"current_labels": {"type": "array", "items": {"type": "string"}},
|
||||
"ai_analysis": {
|
||||
"type": "object",
|
||||
"required": ["truly_stale", "confidence", "reason"],
|
||||
"properties": {
|
||||
"truly_stale": {"type": "boolean"},
|
||||
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
|
||||
"reason": {"type": "string"},
|
||||
"exempt": {"type": "boolean"},
|
||||
"exempt_reason": {"type": ["string", "null"]}
|
||||
}
|
||||
},
|
||||
"recommended_action": {"type": "string", "enum": ["mark_stale", "auto_close", "skip", "needs_review"]},
|
||||
"next_review_date": {"type": ["string", "null"]}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 边界情况
|
||||
|
||||
| 情况 | 处理 |
|
||||
|------|------|
|
||||
| `updated_at` 缺失或为空 | 降级到 `journals` 最后一条的 `created_at`;再次降级到 `created_at` |
|
||||
| 时区异常(如未来时间) | 视为 0 天不活动,跳过 |
|
||||
| `journals` 数组很大(> 100 条) | 仅取最后 5 条用于 AI 判断 |
|
||||
| Issue 没有 `number` 字段 | 跳过,记录到 errors |
|
||||
| API 限流(HTTP 429) | 退避后重试,最多 3 次 |
|
||||
| 网络错误 | 跳过当前 Issue,继续下一个 |
|
||||
| 仓库 archived 或 read-only | 跳过整个仓库,提示用户 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能建议
|
||||
|
||||
| 规模 | 建议 |
|
||||
|------|------|
|
||||
| ≤ 50 个 open Issue | 单次扫描,内存缓存元数据 |
|
||||
| 50-200 个 | 分页拉取,每页 100 条 |
|
||||
| 200-500 个 | 强制分批处理,每批 20 个 |
|
||||
| > 500 个 | 建议夜间运行 + 限定时间范围(如只扫最近 1 年的) |
|
||||
|
||||
API 调用次数:`N_list_pages * 1 + N_candidates * 1 (view) + 1 (tags) ≈ N_candidates + 5`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 参考实现
|
||||
|
||||
伪代码(Python-like):
|
||||
|
||||
```python
|
||||
def scan_stale(owner, repo, stale_days=60, close_days=74):
|
||||
# Step 1: 拉取候选
|
||||
tags = get_repo_tags(owner, repo)
|
||||
issues = list_open_issues(owner, repo)
|
||||
prs = list_open_prs(owner, repo) # 需二次过滤 pull_request_status
|
||||
|
||||
candidates = []
|
||||
|
||||
# Step 2: 时间过滤 + 白名单
|
||||
for issue in issues:
|
||||
days = compute_days_inactive(issue)
|
||||
if days < stale_days:
|
||||
continue
|
||||
if is_exempt(issue, tags):
|
||||
continue
|
||||
candidates.append((issue, days))
|
||||
|
||||
# 同样处理 PRs
|
||||
for pr in prs:
|
||||
days = compute_days_inactive(pr)
|
||||
if days < stale_days:
|
||||
continue
|
||||
# PR 通常没有白名单标签
|
||||
candidates.append((pr, days, "pr"))
|
||||
|
||||
# Step 3: 详情拉取 + AI 判断
|
||||
items = []
|
||||
for item, days, *extra in candidates:
|
||||
detail = view_detail(owner, repo, item.number)
|
||||
analysis = ai_judge_stale(detail)
|
||||
|
||||
items.append({
|
||||
"type": extra[0] if extra else "issue",
|
||||
"number": item.number,
|
||||
"title": item.subject,
|
||||
"days_inactive": days,
|
||||
"ai_analysis": analysis,
|
||||
"recommended_action": decide_final_action(days, analysis, stale_days, close_days)
|
||||
})
|
||||
|
||||
return {
|
||||
"repository": f"{owner}/{repo}",
|
||||
"scanned_at": now_iso(),
|
||||
"thresholds": {"stale_days": stale_days, "close_days": close_days},
|
||||
"summary": summarize(items),
|
||||
"items": items
|
||||
}
|
||||
```
|
||||
|
||||
完整可运行实现请参考 [examples/weekly-cleanup-workflow.md](../examples/weekly-cleanup-workflow.md) 中的 AI Agent 提示词。
|
||||
|
|
@ -0,0 +1,844 @@
|
|||
# gitlink-stale Skill 测试指南
|
||||
|
||||
> 本文档说明如何对 `gitlink-stale` Skill 进行系统性测试,验证其在不同场景下的可用性、正确性和安全性。
|
||||
> 适用测试者:开发者、AI Agent 平台验证人员、课程评审。
|
||||
>
|
||||
> 🪟 **本文档面向 Windows PowerShell 用户**。所有命令均使用 PowerShell 语法,并假设你在 `gitlink-cli` 项目根目录下运行(即 `gitlink-cli.exe` 所在目录)。
|
||||
|
||||
---
|
||||
|
||||
## 📋 测试目标
|
||||
|
||||
| 目标 | 验证内容 |
|
||||
|------|---------|
|
||||
| ✅ 功能正确性 | 扫描、AI 判断、动作执行都符合预期 |
|
||||
| ✅ 安全性 | 写操作前必须用户确认,豁免规则有效 |
|
||||
| ✅ 兼容性 | 在 Claude Code 中可被读取和执行 |
|
||||
| ✅ 健壮性 | 边界情况(空字段、时区、API 异常)处理 |
|
||||
| ✅ 性能 | 批量场景(100+ Issue)的响应时间 |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 测试前准备
|
||||
|
||||
### 0. 命令调用约定(Windows PowerShell)
|
||||
|
||||
> ⚠️ **PowerShell 不会从当前目录加载命令**,所以本地编译的 `gitlink-cli.exe` 必须加 `.\` 前缀调用。
|
||||
|
||||
本指南中所有命令都采用以下两种形式之一:
|
||||
|
||||
| 形式 | 适用场景 |
|
||||
|------|---------|
|
||||
| `.\gitlink-cli.exe <args>` | 本地编译产物,**必须在项目根目录下**运行 |
|
||||
| `gitlink-cli <args>` | 已通过 `npm install -g @gitlink-ai/cli` 全局安装 |
|
||||
|
||||
> 💡 **本文档统一使用 `.\gitlink-cli.exe` 形式**(即假设你用的是项目根目录的编译产物)。
|
||||
> 如果你已经全局安装,把 `.\gitlink-cli.exe` 替换为 `gitlink-cli` 即可。
|
||||
|
||||
**进入项目根目录**:
|
||||
|
||||
```powershell
|
||||
cd D:\code\SE\Evolution_and_Maintenance_of_SE\Mission2\gitlink-cli
|
||||
```
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
```powershell
|
||||
# 1.1 确认 gitlink-cli 已安装并可用
|
||||
.\gitlink-cli.exe version
|
||||
# 期望输出:gitlink-cli dev(本地编译)或 gitlink-cli v0.1.18+(npm 安装)
|
||||
|
||||
# 1.2 完成认证
|
||||
.\gitlink-cli.exe auth login
|
||||
|
||||
# 1.3 验证认证状态
|
||||
.\gitlink-cli.exe auth status
|
||||
# 期望输出:✓ Logged in as <your-login>
|
||||
```
|
||||
|
||||
### 2. 准备测试仓库
|
||||
|
||||
**推荐方案 A — 使用你自己的测试仓库**(建议私有,避免污染公开仓库):
|
||||
|
||||
```powershell
|
||||
# 在 GitLink 上创建测试仓库,然后克隆到本地
|
||||
git clone https://www.gitlink.org.cn/<your-login>/test-stale.git
|
||||
```
|
||||
|
||||
**推荐方案 B — Fork 公开仓库**:
|
||||
|
||||
```powershell
|
||||
.\gitlink-cli.exe repo +fork --owner Gitlink --repo forgeplus
|
||||
# 后续操作在你的 fork 上进行
|
||||
```
|
||||
|
||||
### 3. 准备测试 Issue
|
||||
|
||||
在测试仓库中**手动**创建几个典型 Issue(用于覆盖不同 stale 场景):
|
||||
|
||||
| 编号 | 标题 | 正文要点 | 标签 | 期望动作 |
|
||||
|------|------|---------|------|---------|
|
||||
| #1 | Bug: 登录页卡顿(60+ 天前创建) | 简单描述 | 无 | mark_stale |
|
||||
| #2 | [Roadmap] v2 API 设计 | 长期讨论 | roadmap | skip (exempt) |
|
||||
| #3 | 安全漏洞反馈 | 描述 | security | skip (exempt) |
|
||||
| #4 | 测试 | 仅 2 字符 | 无 | auto_close |
|
||||
| #5 | 希望增加暗色主题 | 描述 | 无 | mark_stale 或 needs_review |
|
||||
| #6 | urgent: 线上故障 | 描述 | 无 | skip (priority 豁免) |
|
||||
| #7 | [WIP] 重构计划 | 描述 | 无 | skip (标题模式豁免) |
|
||||
|
||||
> 💡 为了让 Issue "看起来" 60+ 天未活动,可以:
|
||||
> 1. 创建后**不**评论、**不**修改
|
||||
> 2. 或者用 API 修改 `updated_at` 字段(不推荐,破坏数据真实性)
|
||||
> 3. 推荐做法:调整 `--stale-days` 参数到 1-2 天做快速测试
|
||||
|
||||
---
|
||||
|
||||
## 🎯 测试方法分类
|
||||
|
||||
### 测试维度矩阵
|
||||
|
||||
```
|
||||
┌────────────────────────────────┐
|
||||
│ 测试维度 │
|
||||
└────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
▼ ▼ ▼
|
||||
单元测试 集成测试 E2E 测试
|
||||
(规则验证) (命令执行) (Claude Code)
|
||||
│ │ │
|
||||
├─ 时间计算 ├─ issue +list ├─ 自然语言对话
|
||||
├─ AI 判断规则 ├─ issue +view ├─ 完整工作流
|
||||
├─ 豁免规则 ├─ label-add/remove ├─ 错误恢复
|
||||
└─ 评论模板 ├─ close/comment └─ 跨 Agent 验证
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤖 方法 1:Claude Code 对话测试(主要方法)
|
||||
|
||||
### 测试步骤
|
||||
|
||||
#### Step 1:让 Claude Code 发现并读取 Skill
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请阅读 skills/gitlink-stale/SKILL.md,告诉我这个 Skill 的作用和工作流程。
|
||||
```
|
||||
|
||||
**预期结果**:
|
||||
- Claude Code 能定位文件并完整读取
|
||||
- 用自己的话总结 5 步工作流(扫描 → 时间过滤 → 白名单 → AI 判断 → 应用)
|
||||
- 提及 dry-run 安全机制和 AI 智能判断
|
||||
|
||||
✅ **通过条件**:Claude 准确描述了"扫描 → 豁免 → AI 判断 → 报告 → 确认 → 应用"的核心流程。
|
||||
|
||||
---
|
||||
|
||||
#### Step 2:单 Issue 测试(最基础)
|
||||
|
||||
**测试指令**(在测试仓库目录下):
|
||||
```
|
||||
请使用 gitlink-stale Skill 分析当前仓库的 Issue #1,告诉我处理建议。
|
||||
用 --stale-days 1 参数做快速测试。
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
1. 读取 SKILL.md
|
||||
2. 执行 `.\gitlink-cli.exe issue +view --number 1 --format json`
|
||||
3. 应用 Stage A/B/C 判断
|
||||
4. 输出 JSON 格式的分析结果
|
||||
5. **不**调用任何写 API
|
||||
|
||||
**预期输出示例**:
|
||||
```json
|
||||
{
|
||||
"number": 1,
|
||||
"title": "Bug: 登录页卡顿",
|
||||
"days_inactive": 65,
|
||||
"ai_analysis": {
|
||||
"truly_stale": true,
|
||||
"confidence": 0.85,
|
||||
"reason": "0 评论,维护者从未回复..."
|
||||
},
|
||||
"recommended_action": "mark_stale"
|
||||
}
|
||||
```
|
||||
|
||||
✅ **通过条件**:
|
||||
- 正确判断 days_inactive
|
||||
- 正确识别真僵尸(confidence 合理)
|
||||
- 给出 reasoning 解释
|
||||
- **没有**实际修改 Issue
|
||||
|
||||
---
|
||||
|
||||
#### Step 3:批量扫描测试
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请扫描当前仓库所有 1+ 天未活动的 open Issue(用 --stale-days 1),
|
||||
生成报告后等我确认。
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
1. `.\gitlink-cli.exe issue +list --state open --format json`
|
||||
2. 时间过滤
|
||||
3. 拉取仓库标签(`GET /v1/.../issue_tags.json`)
|
||||
4. 应用白名单豁免
|
||||
5. 逐个详情分析
|
||||
6. 展示 Markdown 表格
|
||||
|
||||
**预期输出示例**:
|
||||
```
|
||||
发现 5 个候选(1+ 天未活动):
|
||||
|
||||
| # | 标题 | 天数 | 置信度 | 动作 | 备注 |
|
||||
|---|------|------|--------|------|------|
|
||||
| 1 | Bug: 登录页卡顿 | 65 | 0.85 | mark_stale | |
|
||||
| 2 | [Roadmap] v2 API | - | - | skip | 豁免: roadmap |
|
||||
| 3 | 安全漏洞反馈 | - | - | skip | 豁免: security |
|
||||
| 4 | 测试 | 80 | 0.95 | auto_close | 强制关闭 |
|
||||
| 5 | 希望增加暗色主题 | 70 | 0.55 | needs_review | ⚠️ 低置信度 |
|
||||
|
||||
是否应用?[yes / 选择性]
|
||||
```
|
||||
|
||||
✅ **通过条件**:
|
||||
- 列出所有 5 个 Issue
|
||||
- 正确豁免 #2 #3
|
||||
- #4 触发强制关闭(标题含"测试")
|
||||
- #5 标记 needs_review
|
||||
- **等待用户确认**,没有自动应用
|
||||
|
||||
---
|
||||
|
||||
#### Step 4:安全规则测试(关键)
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请应用刚才的 stale 报告,不要问我。
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
- **拒绝**直接应用
|
||||
- 回应:"根据 SKILL.md 安全规则,应用前必须用户确认。请回复 yes 或选择性应用(如 #1, #4)"
|
||||
|
||||
✅ **通过条件**:Claude 坚持人在环路,不绕过确认。
|
||||
|
||||
---
|
||||
|
||||
#### Step 5:选择性应用测试
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请只对 #1 执行 mark_stale 动作。
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
1. 备份 #1 的原始字段(`issue +view --format json > before.json`)
|
||||
2. `issue +label-add --labels stale`
|
||||
3. `issue +comment --body "⏰ 长期未活动提醒..."`
|
||||
4. 验证变更已生效
|
||||
|
||||
**预期输出**:
|
||||
```
|
||||
✓ #1 已打 stale 标签
|
||||
✓ 评论已添加:"⏰ 长期未活动提醒..."
|
||||
```
|
||||
|
||||
✅ **通过条件**:
|
||||
- 实际 API 调用成功
|
||||
- 在 GitLink 网页上验证 stale 标签存在
|
||||
- 评论内容符合模板
|
||||
|
||||
---
|
||||
|
||||
#### Step 6:回滚测试
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请回滚 #1 的 stale 标记。
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
1. `issue +label-remove --label stale`
|
||||
2. `issue +comment --body "🙏 抱歉,误判..."`
|
||||
|
||||
✅ **通过条件**:#1 恢复到 stale 处理前状态。
|
||||
|
||||
---
|
||||
|
||||
#### Step 7:AI 判断准确性测试
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请分析以下 3 个 Issue 的 AI 判断准确性:
|
||||
- #2: 含 roadmap 标签 → 应该 skip
|
||||
- #6: urgent 优先级 → 应该 skip
|
||||
- #4: 标题"测试" → 应该 force_close
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
- 准确识别每个 Issue 的关键信号
|
||||
- 在 reasoning 中说明判断依据
|
||||
|
||||
✅ **通过条件**:3 个场景的 AI 判断都符合预期。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 方法 2:命令行手动测试
|
||||
|
||||
### 测试 2.1:基础命令可用性
|
||||
|
||||
```powershell
|
||||
# 1. 列出 Issue
|
||||
.\gitlink-cli.exe issue +list --owner <owner> --repo test-stale --state open --format json
|
||||
|
||||
# 2. 查看单个 Issue
|
||||
.\gitlink-cli.exe issue +view --owner <owner> --repo test-stale --number 1 --format json
|
||||
|
||||
# 3. 获取仓库标签
|
||||
.\gitlink-cli.exe api GET /v1/<owner>/test-stale/issue_tags.json --format json
|
||||
|
||||
# 4. 列出 PR
|
||||
.\gitlink-cli.exe pr +list --owner <owner> --repo test-stale --state open --format json
|
||||
```
|
||||
|
||||
✅ **通过条件**:所有命令返回 200 + 合法 JSON。
|
||||
|
||||
### 测试 2.2:PR 二次过滤验证
|
||||
|
||||
```powershell
|
||||
# 验证 --state 参数不可靠
|
||||
$raw = (& .\gitlink-cli.exe pr +list --owner <owner> --repo test-stale `
|
||||
--state open --format json) | ConvertFrom-Json
|
||||
|
||||
$allCount = $raw.data.pull_requests.Count
|
||||
$openOnly = ($raw.data.pull_requests | Where-Object { $_.pull_request_status -eq 0 }).Count
|
||||
|
||||
Write-Host "Total returned: $allCount (state=open 参数)"
|
||||
Write-Host "Actually open: $openOnly (二次过滤后)"
|
||||
```
|
||||
|
||||
✅ **通过条件**:`$openOnly <= $allCount`,验证二次过滤必要性。
|
||||
|
||||
### 测试 2.3:手动应用 mark_stale
|
||||
|
||||
```powershell
|
||||
# 备份
|
||||
$ts = Get-Date -Format "yyyyMMddHHmmss"
|
||||
.\gitlink-cli.exe issue +view --owner <owner> --repo test-stale `
|
||||
--number 1 --format json |
|
||||
Out-File -Encoding utf8 "$env:TEMP\before-stale-$ts.json"
|
||||
|
||||
# 打 stale 标签
|
||||
.\gitlink-cli.exe issue +label-add `
|
||||
--owner <owner> --repo test-stale `
|
||||
--number 1 --labels "stale"
|
||||
|
||||
# 评论催办
|
||||
$body = @"
|
||||
⏰ **长期未活动提醒**
|
||||
|
||||
本 Issue 已 60 天未收到新回复,暂时标记为 ``stale``。
|
||||
"@
|
||||
.\gitlink-cli.exe issue +comment `
|
||||
--owner <owner> --repo test-stale `
|
||||
--number 1 --body $body
|
||||
|
||||
# 验证
|
||||
.\gitlink-cli.exe issue +view --owner <owner> --repo test-stale `
|
||||
--number 1 --format json |
|
||||
ConvertFrom-Json |
|
||||
Select-Object -ExpandProperty data |
|
||||
Select-Object number, @{N="labels";E={$_.issue_tags.name -join ","}}, @{N="journals";E={$_.journals.Count}}
|
||||
```
|
||||
|
||||
✅ **通过条件**:
|
||||
- labels 含 "stale"
|
||||
- journals 数量增加 1
|
||||
- 备份文件存在
|
||||
|
||||
### 测试 2.4:dry-run 验证
|
||||
|
||||
```powershell
|
||||
# 用 dry-run 测试批量关闭(确认 dry-run 机制本身可用)
|
||||
.\gitlink-cli.exe issue +batch-close `
|
||||
--owner <owner> --repo test-stale `
|
||||
--numbers 999,998 --dry-run
|
||||
# 期望:输出"planned",不实际关闭
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 方法 3:边界情况测试
|
||||
|
||||
### 测试 3.1:updated_at 缺失
|
||||
|
||||
**场景**:某些老 Issue 可能 `updated_at` 字段缺失或异常。
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请分析仓库中一个 updated_at 字段缺失的 Issue。
|
||||
```
|
||||
|
||||
**预期行为**:
|
||||
- 降级到 journals 最后一条的 created_at
|
||||
- 再次降级到 created_at
|
||||
- 在报告中标记"时间字段降级"
|
||||
|
||||
### 测试 3.2:时区异常
|
||||
|
||||
**场景**:updated_at 是未来时间(时区错误)。
|
||||
|
||||
**预期行为**:视为 0 天不活动,跳过。
|
||||
|
||||
### 测试 3.3:超大 journals 数组
|
||||
|
||||
**场景**:某 Issue 有 100+ 条评论。
|
||||
|
||||
**预期行为**:仅取最后 5 条用于 AI 判断,不超时。
|
||||
|
||||
### 测试 3.4:仓库无 stale 标签
|
||||
|
||||
**场景**:仓库未预先创建 stale 标签。
|
||||
|
||||
**预期行为**:
|
||||
- label-add 失败时清晰提示
|
||||
- 不影响其他动作(如 comment)
|
||||
|
||||
### 测试 3.5:Token 失效模拟
|
||||
|
||||
```powershell
|
||||
Remove-Item Env:GITLINK_TOKEN -ErrorAction SilentlyContinue
|
||||
.\gitlink-cli.exe auth logout
|
||||
```
|
||||
|
||||
**测试指令**:
|
||||
```
|
||||
请应用 #1 的 stale 标记。
|
||||
```
|
||||
|
||||
**预期 Claude Code 行为**:
|
||||
- 检测到 HTTP 401
|
||||
- 提示:"Token 失效,请运行 `.\gitlink-cli.exe auth login`"
|
||||
- **不**继续后续操作
|
||||
|
||||
### 测试 3.6:标题含 emoji
|
||||
|
||||
**场景**:Issue 标题如 "🐛 Bug: 登录失败"。
|
||||
|
||||
**预期行为**:跳过 emoji 字符后做关键词匹配。
|
||||
|
||||
### 测试 3.7:跨语言评论
|
||||
|
||||
**场景**:评论中英文混合:"已 fixed in main branch, please verify"。
|
||||
|
||||
**预期行为**:识别 "fixed" 关键词,建议关闭。
|
||||
|
||||
---
|
||||
|
||||
## 📊 方法 4:自动化测试脚本
|
||||
|
||||
把以下内容保存为 `test-stale.ps1`:
|
||||
|
||||
```powershell
|
||||
# test-stale.ps1 — gitlink-stale 自动化冒烟测试 (Windows PowerShell)
|
||||
# 用法: .\test-stale.ps1 -Owner <owner> -Repo <repo> [-StaleDays 1]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Owner,
|
||||
[Parameter(Mandatory=$true)][string]$Repo,
|
||||
[int]$StaleDays = 60
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
$Pass = 0
|
||||
$Fail = 0
|
||||
$FailedTests = @()
|
||||
|
||||
function Assert {
|
||||
param([string]$Desc, [bool]$Condition)
|
||||
if ($Condition) {
|
||||
Write-Host " ✅ $Desc" -ForegroundColor Green
|
||||
$script:Pass++
|
||||
} else {
|
||||
Write-Host " ❌ $Desc" -ForegroundColor Red
|
||||
$script:Fail++
|
||||
$script:FailedTests += $Desc
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "=== Testing gitlink-stale on $Owner/$Repo (stale_days=$StaleDays) ===" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# TC-01: 基础读取
|
||||
Write-Host "TC-01: 基础命令"
|
||||
try {
|
||||
$result = & .\gitlink-cli.exe issue +list --owner $Owner --repo $Repo --state open --format json 2>&1
|
||||
Assert "issue +list 返回 0" ($LASTEXITCODE -eq 0)
|
||||
$parsed = $result | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
Assert "返回 JSON 含 issues 字段" ($parsed.data.issues -ne $null)
|
||||
} catch {
|
||||
Assert "issue +list 返回 0" $false
|
||||
}
|
||||
|
||||
# TC-02: 标签 API
|
||||
Write-Host "TC-02: 仓库标签"
|
||||
try {
|
||||
& .\gitlink-cli.exe api GET "/v1/$Owner/$Repo/issue_tags.json" --format json 2>&1 | Out-Null
|
||||
Assert "issue_tags.json 可访问" ($LASTEXITCODE -eq 0)
|
||||
} catch {
|
||||
Assert "issue_tags.json 可访问" $false
|
||||
}
|
||||
|
||||
# TC-03: PR 列表 + 二次过滤
|
||||
Write-Host "TC-03: PR 列表二次过滤"
|
||||
try {
|
||||
$prRaw = & .\gitlink-cli.exe pr +list --owner $Owner --repo $Repo --state open --format json 2>&1
|
||||
$prObj = $prRaw | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
if ($prObj.data.pull_requests) {
|
||||
$totalReturned = $prObj.data.pull_requests.Count
|
||||
$openOnly = ($prObj.data.pull_requests | Where-Object { $_.pull_request_status -eq 0 }).Count
|
||||
Write-Host " 返回 $totalReturned 个,实际 open $openOnly 个"
|
||||
Assert "二次过滤生效" ($openOnly -le $totalReturned)
|
||||
} else {
|
||||
Assert "PR 列表可获取" $true
|
||||
}
|
||||
} catch {
|
||||
Assert "PR 列表二次过滤" $false
|
||||
}
|
||||
|
||||
# TC-04: 单 Issue 详情
|
||||
Write-Host "TC-04: Issue 详情"
|
||||
$listRaw = & .\gitlink-cli.exe issue +list --owner $Owner --repo $Repo --format json 2>&1
|
||||
$listObj = $listRaw | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
if ($listObj.data.issues.Count -gt 0) {
|
||||
$num = $listObj.data.issues[0].number
|
||||
$viewRaw = & .\gitlink-cli.exe issue +view --owner $Owner --repo $Repo --number $num --format json 2>&1
|
||||
$viewObj = $viewRaw | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
Assert "issue +view 返回详情" ($viewObj.data.subject -ne $null)
|
||||
Assert "详情含 journals 字段" ($viewObj.data.journals -ne $null)
|
||||
} else {
|
||||
Assert "存在可测试的 Issue" $false
|
||||
}
|
||||
|
||||
# TC-05: 时间计算
|
||||
Write-Host "TC-05: 时间过滤"
|
||||
$threshold = (Get-Date).AddDays(-$StaleDays)
|
||||
$staleCount = ($listObj.data.issues | Where-Object {
|
||||
$updated = if ($_.updated_at) { [DateTime]::Parse($_.updated_at) } else { [DateTime]::Parse($_.created_at) }
|
||||
$updated -lt $threshold
|
||||
}).Count
|
||||
Write-Host " 发现 $staleCount 个 $StaleDays+ 天未活动的 Issue"
|
||||
Assert "时间过滤可执行" ($staleCount -ge 0)
|
||||
|
||||
# TC-06: dry-run 安全
|
||||
Write-Host "TC-06: dry-run 机制"
|
||||
$dryRaw = & .\gitlink-cli.exe issue +batch-close --owner $Owner --repo $Repo --numbers 999999 --dry-run 2>&1
|
||||
$dryObj = $dryRaw | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
Assert "dry-run 不实际执行" ($dryObj.data.dry_run -eq $true)
|
||||
|
||||
# 总结
|
||||
Write-Host ""
|
||||
Write-Host "=== Summary ===" -ForegroundColor Cyan
|
||||
Write-Host "Passed: $Pass"
|
||||
Write-Host "Failed: $Fail"
|
||||
if ($Fail -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Failed tests:" -ForegroundColor Red
|
||||
foreach ($t in $FailedTests) { Write-Host " - $t" }
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
使用方法:
|
||||
|
||||
```powershell
|
||||
# 放行当前会话执行策略
|
||||
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
||||
|
||||
# 快速测试(1 天阈值)
|
||||
.\test-stale.ps1 -Owner <your-login> -Repo test-stale -StaleDays 1
|
||||
|
||||
# 标准测试(60 天阈值)
|
||||
.\test-stale.ps1 -Owner <your-login> -Repo test-stale
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎓 方法 5:完整 E2E 测试剧本
|
||||
|
||||
> 这是给评审者看的完整测试流程,复制粘贴给 Claude Code 即可执行。
|
||||
|
||||
### 完整测试剧本
|
||||
|
||||
```
|
||||
我需要对 <owner>/<repo> 仓库的 Issue 执行 gitlink-stale 完整测试。
|
||||
请按以下步骤执行:
|
||||
|
||||
【准备阶段】
|
||||
1. 阅读 skills/gitlink-stale/SKILL.md,确认你理解工作流
|
||||
2. 列出仓库中所有 open 状态的 Issue(编号、标题、当前 labels)
|
||||
3. 列出仓库可用标签
|
||||
4. 确认仓库是否有 "stale" 标签
|
||||
|
||||
【扫描阶段】(用 --stale-days 1 做快速测试)
|
||||
5. 应用时间过滤,找出 1+ 天未活动的 Issue
|
||||
6. 应用白名单豁免,排除 pinned/security/roadmap
|
||||
7. 对每个候选项拉取详情(issue +view --number N)
|
||||
8. AI 判断真假僵尸(journals、tracker、作者活跃度)
|
||||
9. 生成 JSON 报告
|
||||
10. 用 Markdown 表格展示决策摘要
|
||||
11. 高亮 confidence < 0.6 的项(needs_review)
|
||||
|
||||
【确认阶段】
|
||||
12. 问我"是否应用?",等我回复
|
||||
|
||||
【应用阶段】(仅在我回复 yes 后)
|
||||
13. 备份原始字段到 $env:TEMP\before-stale-<timestamp>.json
|
||||
14. 对每个高置信度 Issue 执行:
|
||||
- issue +label-add --labels "stale"
|
||||
- issue +comment --body "<催办模板>"
|
||||
- 若 auto_close: issue +close
|
||||
15. 完成后输出统计:成功数、失败数、跳过数
|
||||
|
||||
【验证阶段】
|
||||
16. 重新 GET 每个已应用的 Issue,确认标签和评论存在
|
||||
17. 生成审计日志 $env:TEMP\stale-audit-<timestamp>.json
|
||||
|
||||
注意:本项目在 Windows 上测试,请用 .\gitlink-cli.exe 而非 gitlink-cli。
|
||||
每一步都告诉我你在做什么,遇到错误立即停下来问我。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 测试用例清单(Checklist)
|
||||
|
||||
测试时逐项打勾:
|
||||
|
||||
### 基础功能
|
||||
|
||||
- [ ] **TC-01** Claude Code 能读取 SKILL.md 并理解工作流
|
||||
- [ ] **TC-02** 单 Issue 分析输出符合 JSON schema
|
||||
- [ ] **TC-03** 批量扫描生成完整报告
|
||||
- [ ] **TC-04** 时间计算正确(含 updated_at 缺失降级)
|
||||
- [ ] **TC-05** 白名单豁免规则生效(pinned/security/roadmap)
|
||||
- [ ] **TC-06** 标题强制关闭模式匹配("测试"等)
|
||||
- [ ] **TC-07** AI 真假僵尸判断准确
|
||||
- [ ] **TC-08** PR 二次过滤(pull_request_status == 0)
|
||||
|
||||
### 安全规则
|
||||
|
||||
- [ ] **TC-09** 扫描阶段零写 API 调用
|
||||
- [ ] **TC-10** 应用前必须用户确认
|
||||
- [ ] **TC-11** "不要问我"指令被拒绝
|
||||
- [ ] **TC-12** urgent/roadmap Issue 永不被处理
|
||||
- [ ] **TC-13** 字段快照已保留(可回滚)
|
||||
- [ ] **TC-14** 低置信度(<0.6)项不自动处理
|
||||
|
||||
### 应用与回滚
|
||||
|
||||
- [ ] **TC-15** label-add 添加 stale 标签成功
|
||||
- [ ] **TC-16** comment 评论内容符合模板
|
||||
- [ ] **TC-17** close 关闭 Issue 成功
|
||||
- [ ] **TC-18** 回滚后 stale 标签已移除
|
||||
- [ ] **TC-19** 误关 Issue 可重新打开
|
||||
|
||||
### 边界情况
|
||||
|
||||
- [ ] **TC-20** updated_at 缺失时降级到 journals/created_at
|
||||
- [ ] **TC-21** 超大 journals(100+ 条)不超时
|
||||
- [ ] **TC-22** 标题含 emoji 正常处理
|
||||
- [ ] **TC-23** 跨语言评论正常识别
|
||||
- [ ] **TC-24** 仓库无 stale 标签时优雅提示
|
||||
|
||||
### 错误处理
|
||||
|
||||
- [ ] **TC-25** HTTP 401 → 提示重新登录
|
||||
- [ ] **TC-26** HTTP 403 → 提示权限不足
|
||||
- [ ] **TC-27** HTTP 404 → 跳过并记录
|
||||
- [ ] **TC-28** 网络错误 → 重试或停止
|
||||
- [ ] **TC-29** API 限流(429)→ 退避
|
||||
|
||||
### 性能
|
||||
|
||||
- [ ] **TC-30** 单 Issue 分析 < 10s
|
||||
- [ ] **TC-31** 20 Issue 批量分析 < 3 分钟
|
||||
- [ ] **TC-32** 应用 20 Issue < 1 分钟
|
||||
- [ ] **TC-33** 无 API 限流(429)
|
||||
|
||||
---
|
||||
|
||||
## 📝 测试报告模板
|
||||
|
||||
完成测试后,填写以下报告(保存到 `doc\stale-test-result-<date>.md`):
|
||||
|
||||
```markdown
|
||||
# gitlink-stale 测试报告
|
||||
|
||||
**测试日期**: YYYY-MM-DD
|
||||
**测试者**: <name>
|
||||
**测试仓库**: <owner>/<repo>
|
||||
**Agent 平台**: Claude Code v<version>
|
||||
**操作系统**: Windows <version> + PowerShell <version>
|
||||
|
||||
## 测试结果
|
||||
|
||||
| 类别 | 总数 | 通过 | 失败 |
|
||||
|------|------|------|------|
|
||||
| 基础功能 | 8 | ? | ? |
|
||||
| 安全规则 | 6 | ? | ? |
|
||||
| 应用与回滚 | 5 | ? | ? |
|
||||
| 边界情况 | 5 | ? | ? |
|
||||
| 错误处理 | 5 | ? | ? |
|
||||
| 性能 | 4 | ? | ? |
|
||||
| **总计** | **33** | **?** | **?** |
|
||||
|
||||
## 关键发现
|
||||
|
||||
(记录测试中观察到的问题或亮点)
|
||||
|
||||
## AI 判断准确率
|
||||
|
||||
- 真僵尸识别准确率:?%
|
||||
- 误关率:?%
|
||||
- 漏关率:?%
|
||||
|
||||
## 截图证据
|
||||
|
||||
(附 Claude Code 对话截图、GitLink 网页字段变更截图)
|
||||
|
||||
## 结论
|
||||
|
||||
- [ ] 生产就绪
|
||||
- [ ] 需要修复后再测
|
||||
- [ ] 严重问题,重新设计
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 常见测试陷阱
|
||||
|
||||
### 陷阱 1:在公开仓库测试污染
|
||||
|
||||
❌ **错误做法**:直接在 `Gitlink/forgeplus` 等公开仓库测试写操作。
|
||||
|
||||
✅ **正确做法**:使用自己的测试仓库(建议私有)。
|
||||
|
||||
### 陷阱 2:忘记 dry-run 导致 Issue 被关
|
||||
|
||||
❌ **错误做法**:直接让 Claude 应用,结果发现误关。
|
||||
|
||||
✅ **正确做法**:始终先要求"只生成报告",确认后再应用。
|
||||
|
||||
### 陷阱 3:PR 二次过滤缺失
|
||||
|
||||
❌ **错误做法**:信任 `pr +list --state open` 的过滤,把 merged PR 也纳入候选。
|
||||
|
||||
✅ **正确做法**:客户端按 `pull_request_status == 0` 二次过滤。
|
||||
|
||||
### 陷阱 4:备份文件被覆盖
|
||||
|
||||
❌ **错误做法**:所有备份都写到 `$env:TEMP\before.json`,多次测试后丢失。
|
||||
|
||||
✅ **正确做法**:备份文件名加时间戳:
|
||||
```powershell
|
||||
$ts = Get-Date -Format "yyyyMMddHHmmss"
|
||||
.\gitlink-cli.exe issue +list ... |
|
||||
Out-File -Encoding utf8 "$env:TEMP\before-stale-$ts.json"
|
||||
```
|
||||
|
||||
### 陷阱 5:测试后忘记清理 stale 标签
|
||||
|
||||
❌ **错误做法**:测试 Issue 留着 stale 标签,下次扫描会再次处理。
|
||||
|
||||
✅ **正确做法**:测试结束后回滚(label-remove)或关闭测试 Issue。
|
||||
|
||||
### 陷阱 6:PowerShell 执行策略阻止脚本
|
||||
|
||||
❌ **错误做法**:直接 `.\test-stale.ps1` 报"无法加载,未签名"。
|
||||
|
||||
✅ **正确做法**:放行当前会话执行策略:
|
||||
```powershell
|
||||
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
|
||||
```
|
||||
|
||||
### 陷阱 7:忘记 `.\` 前缀
|
||||
|
||||
❌ **错误做法**:在项目根目录下输入 `gitlink-cli version`,报"未识别命令"。
|
||||
|
||||
✅ **正确做法**:PowerShell 不从当前目录加载命令,必须 `.\gitlink-cli.exe version`。
|
||||
|
||||
### 陷阱 8:stale 阈值设置过严
|
||||
|
||||
❌ **错误做法**:用默认 60 天阈值,测试仓库的所有 Issue 都未达阈值。
|
||||
|
||||
✅ **正确做法**:快速测试时传 `--stale-days 1`,或在 AI 提示词中明确说"用 1 天阈值"。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 推荐测试顺序
|
||||
|
||||
```
|
||||
1. 准备环境(5 分钟)
|
||||
↓
|
||||
2. 命令行冒烟测试(10 分钟)—— 方法 2 + 方法 4 脚本
|
||||
↓
|
||||
3. Claude Code 单 Issue 测试(5 分钟)—— 方法 1 Step 1-2
|
||||
↓
|
||||
4. Claude Code 批量测试(15 分钟)—— 方法 1 Step 3-5
|
||||
↓
|
||||
5. 安全规则测试(5 分钟)—— 方法 1 Step 4
|
||||
↓
|
||||
6. 边界情况测试(15 分钟)—— 方法 3
|
||||
↓
|
||||
7. AI 判断测试(10 分钟)—— 方法 1 Step 7
|
||||
↓
|
||||
8. 回滚测试(5 分钟)—— 方法 1 Step 6
|
||||
↓
|
||||
9. 填写测试报告(10 分钟)
|
||||
```
|
||||
|
||||
**总耗时**:约 80 分钟
|
||||
|
||||
---
|
||||
|
||||
## 📞 测试支持
|
||||
|
||||
遇到问题时:
|
||||
|
||||
1. **查阅文档**:
|
||||
- [SKILL.md](./SKILL.md) — 工作流总览
|
||||
- [references/gitlink-stale-scan.md](./references/gitlink-stale-scan.md) — 扫描算法
|
||||
- [references/gitlink-stale-judge.md](./references/gitlink-stale-judge.md) — AI 判断规则
|
||||
- [references/gitlink-stale-actions.md](./references/gitlink-stale-actions.md) — 应用手册
|
||||
- [references/gitlink-stale-exempt.md](./references/gitlink-stale-exempt.md) — 豁免规则
|
||||
|
||||
2. **查阅示例**:
|
||||
- [examples/weekly-cleanup-workflow.md](./examples/weekly-cleanup-workflow.md) — 完整工作流
|
||||
- [examples/pr-stale-workflow.md](./examples/pr-stale-workflow.md) — PR 处理
|
||||
- [examples/ai-judgment-demo.md](./examples/ai-judgment-demo.md) — AI 判断演示
|
||||
|
||||
3. **运行自动化脚本**:
|
||||
- 见本文档 §方法 4
|
||||
|
||||
4. **直接询问 Claude Code**:
|
||||
```
|
||||
我在测试 gitlink-stale 时遇到 <具体问题>,请帮我诊断。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 通过标准
|
||||
|
||||
测试要算"通过",必须满足:
|
||||
|
||||
- [ ] **33 个测试用例**全部通过(或失败项有合理的 workaround)
|
||||
- [ ] **无安全规则违反**(dry-run 被绕过、未确认就写入等)
|
||||
- [ ] **白名单豁免有效**(pinned/security/roadmap Issue 永不处理)
|
||||
- [ ] **AI 判断准确率 ≥ 80%**(20+ Issue 上测试)
|
||||
- [ ] **Claude Code 集成可用**(自然语言指令能触发完整工作流)
|
||||
- [ ] **测试报告完整填写**(含截图证据)
|
||||
|
||||
达到以上标准即可认为是生产就绪的 Skill。
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: gitlink-wiki
|
||||
version: 1.0.0
|
||||
description: "Wiki 管理:查看、创建、更新、删除 Wiki 页面。当用户需要操作 GitLink Wiki 时触发。"
|
||||
version: 1.1.0
|
||||
description: "Wiki 管理:查看、创建、更新、删除 Wiki 页面、质量检查(lint)。当用户需要操作 GitLink Wiki 时触发。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["gitlink-cli"]
|
||||
|
|
@ -13,6 +13,7 @@ metadata:
|
|||
**CRITICAL — 开始前必须先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md),其中包含认证、权限处理和 API 注意事项。**
|
||||
**CRITICAL — 所有 Shortcuts 在执行写入/删除操作前,务必先确认用户意图。**
|
||||
**CRITICAL — GitLink 操作只能用 `gitlink-cli`。禁止用 `gh`(GitHub CLI)操作 GitLink 资源。`gh` 仅适用于 GitHub 平台。**
|
||||
**注意:`wiki +lint` 当前仅在本地编译版本中可用,需 `go build -o gitlink-cli.exe .` 后使用 `./gitlink-cli.exe`。**
|
||||
|
||||
> **前置条件:** 先阅读 [`../gitlink-shared/SKILL.md`](../gitlink-shared/SKILL.md)
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ metadata:
|
|||
| `wiki +create` | 创建 Wiki 页面,支持 `--dry-run` 预览 | 是 |
|
||||
| `wiki +update` | 更新 Wiki 页面,支持 `--dry-run` 预览 | 是 |
|
||||
| `wiki +delete` | 删除 Wiki 页面,支持 `--dry-run` 预览 | 是 |
|
||||
| `wiki +lint` | 检查 Wiki 页面质量问题(链接、标题、图片、空白页) | 否 |
|
||||
|
||||
## 使用示例
|
||||
|
||||
|
|
@ -64,6 +66,12 @@ gitlink-cli wiki +delete --owner myuser --repo myrepo --title "废弃页面"
|
|||
|
||||
# 预览删除操作
|
||||
gitlink-cli wiki +delete --owner myuser --repo myrepo --title "废弃页面" --dry-run
|
||||
|
||||
# 检查 Wiki 页面质量问题(全部检查)
|
||||
gitlink-cli wiki +lint --owner myuser --repo myrepo
|
||||
|
||||
# 只检查链接和空白页
|
||||
gitlink-cli wiki +lint --owner myuser --repo myrepo --check links,empty
|
||||
```
|
||||
|
||||
## Wiki 页面内容格式
|
||||
|
|
|
|||
|
|
@ -58,16 +58,7 @@ gitlink-cli api POST /:owner/:repo/pulls/:id/reviews --body '{"body":"代码审
|
|||
|
||||
**场景**:从提交历史自动生成版本发布说明。
|
||||
|
||||
```bash
|
||||
# 1. 获取两个版本之间的提交
|
||||
gitlink-cli api GET /:owner/:repo/compare/:base...:head --format json
|
||||
|
||||
# 2. 获取已关闭的 Issue
|
||||
gitlink-cli issue +list --state closed --format json
|
||||
|
||||
# 3. 生成 Release Notes 并创建发布
|
||||
gitlink-cli release +create --tag v1.2.0 --name "v1.2.0" --body "## What's Changed\n- feat: 新功能 (#123)\n- fix: 修复问题 (#456)"
|
||||
```
|
||||
> 此工作流已独立为 [`gitlink-changelog`](../gitlink-changelog/SKILL.md) skill,包含完整的数据收集、分类规则、模板和发布流程。详见该 skill 的 [references/](../gitlink-changelog/references/) 和 [examples/](../gitlink-changelog/examples/)。
|
||||
|
||||
## 工作流 4:Repo Setup(仓库初始化)
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue