feat: 提交详情能看 diff,也能丢给 AI 审这一次改动。文件检查可单独排除路径,TODO 只认大写,避免把 todo 表和模块当成待办标记。
热力图按容器宽度铺满一年,不再横向滚动。托盘右键换成可换皮肤的弹层;更新下载显示进度,退出后再由脚本拉起安装器,避免还占着 exe。
This commit is contained in:
@@ -21,7 +21,7 @@ const (
|
||||
// 它不会重新遍历整个仓库,只读取已记录的小型源码文件,用于发现 TODO、长文件等轻量风险。
|
||||
type InsightService struct{}
|
||||
|
||||
func (InsightService) Analyze(ctx context.Context, project model.Project, structure model.StructureStats, git model.GitStats) (model.ProjectInsights, error) {
|
||||
func (InsightService) Analyze(ctx context.Context, project model.Project, structure model.StructureStats, git model.GitStats, excludes []model.ExclusionRule) (model.ProjectInsights, error) {
|
||||
desc, e := platform.ResolvePath(project.Path)
|
||||
if e != nil {
|
||||
return model.ProjectInsights{}, e
|
||||
@@ -40,6 +40,7 @@ func (InsightService) Analyze(ctx context.Context, project model.Project, struct
|
||||
}
|
||||
|
||||
checkLanguageHealth(project.Languages, add)
|
||||
structure = filterStructureForInsights(structure, excludes)
|
||||
out.Summary.LargeFiles = len(structure.LargeFiles)
|
||||
checkStructureHealth(structure, add)
|
||||
checkGitHealth(git, add)
|
||||
@@ -162,7 +163,7 @@ func inspectTextFile(path string) (int, []model.InsightIssue) {
|
||||
kind, severity = "FIXME", "medium"
|
||||
case strings.Contains(upper, "HACK"):
|
||||
kind, severity = "HACK", "medium"
|
||||
case strings.Contains(upper, "TODO"):
|
||||
case hasUpperTODO(text):
|
||||
kind = "TODO"
|
||||
}
|
||||
if kind != "" {
|
||||
@@ -172,6 +173,70 @@ func inspectTextFile(path string) (int, []model.InsightIssue) {
|
||||
return line, issues
|
||||
}
|
||||
|
||||
func filterStructureForInsights(s model.StructureStats, rules []model.ExclusionRule) model.StructureStats {
|
||||
if len(rules) == 0 {
|
||||
return s
|
||||
}
|
||||
out := s
|
||||
out.Files = out.Files[:0:0]
|
||||
out.LargeFiles = out.LargeFiles[:0:0]
|
||||
out.Folders = out.Folders[:0:0]
|
||||
for _, f := range s.Files {
|
||||
if pathExcluded(f.Path, rules) {
|
||||
continue
|
||||
}
|
||||
out.Files = append(out.Files, f)
|
||||
}
|
||||
for _, f := range s.LargeFiles {
|
||||
if pathExcluded(f.Path, rules) {
|
||||
continue
|
||||
}
|
||||
out.LargeFiles = append(out.LargeFiles, f)
|
||||
}
|
||||
for _, f := range s.Folders {
|
||||
if pathExcluded(f.Name, rules) {
|
||||
continue
|
||||
}
|
||||
out.Folders = append(out.Folders, f)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func pathExcluded(path string, rules []model.ExclusionRule) bool {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" || len(rules) == 0 {
|
||||
return false
|
||||
}
|
||||
slash := filepath.ToSlash(path)
|
||||
for _, r := range rules {
|
||||
if MatchExcludedPattern(r.Pattern, slash) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasUpperTODO 只认大写 TODO 标记,避免命中 todo 表名、模块名或变量。
|
||||
func hasUpperTODO(text string) bool {
|
||||
for i := 0; i <= len(text)-4; i++ {
|
||||
if text[i:i+4] != "TODO" {
|
||||
continue
|
||||
}
|
||||
if i > 0 && isIdentByte(text[i-1]) {
|
||||
continue
|
||||
}
|
||||
if i+4 < len(text) && isIdentByte(text[i+4]) {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isIdentByte(b byte) bool {
|
||||
return b == '_' || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9')
|
||||
}
|
||||
|
||||
func isInsightTextFile(ext string) bool {
|
||||
switch strings.ToLower(ext) {
|
||||
case ".go", ".php", ".js", ".ts", ".vue", ".jsx", ".tsx", ".css", ".scss", ".sass", ".less", ".html", ".md", ".json", ".yaml", ".yml", ".xml", ".sql", ".py", ".java", ".cs", ".rb", ".rs", ".c", ".cpp", ".h":
|
||||
|
||||
Reference in New Issue
Block a user