wavelet init

This commit is contained in:
ryan
2026-06-18 15:24:48 +08:00
parent d6a7011885
commit 99738bbc17
714 changed files with 139987 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
---
name: go-code-review
description: Use when reviewing Go code or checking code against community style standards. Also use proactively before submitting a Go PR or when reviewing any Go code changes, even if the user doesn't explicitly request a style review. Does not cover language-specific syntax — delegates to specialized skills.
license: Apache-2.0
compatibility: Web server example in references uses slog (Go 1.21+)
metadata:
sources: "Go Wiki CodeReviewComments, Uber Style Guide"
allowed-tools: Bash(bash:*)
---
# Go 代码审查清单
## 审查流程
> 使用 `assets/review-template.md` 格式化代码审查输出,确保结构与"必须修复 / 建议修复 / 吹毛求疵"的严重程度分组保持一致。
1. 运行 `gofmt -d .` 和 `go vet ./...` 先捕获机械性问题
2. 逐文件阅读 diff;对于每个文件,按以下类别顺序检查
3. 标记问题时需要包含具体行号引用和规则名称
4. 审查完所有文件后,重新阅读标记项以确认它们是真实的问题
5. 按严重程度分组汇总发现(必须修复、建议修复、吹毛求疵)
> **验证**:完成审查后,再次阅读 diff 以验证每个标记的问题都是真实的。删除任何无法用具体行号引用的发现。
---
## 格式化
- [ ] **gofmt**:代码已使用 `gofmt` 或 `goimports` 格式化 → [go-linting](../go-linting/SKILL.md)
---
## 文档
- [ ] **注释句子**:注释是完整的句子,以被描述的名称开头,以句号结尾 → [go-documentation](../go-documentation/SKILL.md)
- [ ] **文档注释**:所有导出名称都有文档注释;非平凡的未导出声明也应有 → [go-documentation](../go-documentation/SKILL.md)
- [ ] **包注释**:包注释出现在 package 子句附近,无空行 → [go-documentation](../go-documentation/SKILL.md)
- [ ] **命名结果参数**:仅当它们能澄清含义时使用(例如,多个相同类型返回值),而不仅仅是为了启用裸返回 → [go-documentation](../go-documentation/SKILL.md)
---
## 错误处理
- [ ] **处理错误**:不使用 `_` 丢弃错误;处理、返回或(在特殊情况下)panic → [go-error-handling](../go-error-handling/SKILL.md)
- [ ] **错误字符串**:小写开头,无标点(除非以专有名词/首字母缩略词开头) → [go-error-handling](../go-error-handling/SKILL.md)
- [ ] **带内错误**:不使用魔术值(-1、""、nil);使用带 error 或 ok bool 的多返回值 → [go-error-handling](../go-error-handling/SKILL.md)
- [ ] **错误流缩进**:先处理错误并返回;保持正常路径的缩进最小化 → [go-error-handling](../go-error-handling/SKILL.md)
---
## 命名
- [ ] **MixedCaps**:使用 `MixedCaps` 或 `mixedCaps`,不使用下划线;未导出使用 `maxLength` 而非 `MAX_LENGTH` → [go-naming](../go-naming/SKILL.md)
- [ ] **首字母缩略词**:保持一致的大小写:`URL`/`url`、`ID`/`id`、`HTTP`/`http`(例如 `ServeHTTP`、`xmlHTTPRequest`) → [go-naming](../go-naming/SKILL.md)
- [ ] **变量名**:有限作用域用短名称(`i`、`r`、`c`);更广作用域用较长名称 → [go-naming](../go-naming/SKILL.md)
- [ ] **接收器名称**:类型的一两个字母缩写(`c` 代表 `Client`);不使用 `this`、`self`、`me`;各方法之间保持一致 → [go-naming](../go-naming/SKILL.md)
- [ ] **包名**:不重复(使用 `chubby.File` 而非 `chubby.ChubbyFile`);避免 `util`、`common`、`misc` → [go-packages](../go-packages/SKILL.md)
- [ ] **避免内置名称**:不遮蔽 `error`、`string`、`len`、`cap`、`append`、`copy`、`new`、`make` → [go-declarations](../go-declarations/SKILL.md)
---
## 并发
- [ ] **Goroutine 生命周期**:明确 goroutine 何时/是否退出;如不明显则添加文档 → [go-concurrency](../go-concurrency/SKILL.md)
- [ ] **同步函数**:优先同步而非异步;让调用者在需要时添加并发 → [go-concurrency](../go-concurrency/SKILL.md)
- [ ] **Context**:作为第一个参数;不放在 struct 中;不自定义 Context 类型;即使认为不需要也应传递 → [go-context](../go-context/SKILL.md)
---
## 接口
- [ ] **接口位置**:在消费方包中定义,而非实现方;生产者返回具体类型 → [go-interfaces](../go-interfaces/SKILL.md)
- [ ] **不提前定义接口**:不在使用前定义;不在实现方"为了 mock"而定义 → [go-interfaces](../go-interfaces/SKILL.md)
- [ ] **接收器类型**:如果会修改状态、有 sync 字段或体积大,使用指针;小的不可变类型使用值;不要混用 → [go-interfaces](../go-interfaces/SKILL.md)
---
## 数据结构
- [ ] **空切片**:优先使用 `var t []string`(nil)而非 `t := []string{}`(非 nil 零长度) → [go-data-structures](../go-data-structures/SKILL.md)
- [ ] **复制**:小心复制含指针/切片字段的结构体;不按值复制 `*T` 方法的接收器 → [go-data-structures](../go-data-structures/SKILL.md)
---
## 安全性
- [ ] **加密随机数**:密钥使用 `crypto/rand`,不使用 `math/rand` → [go-defensive](../go-defensive/SKILL.md)
- [ ] **不 panic**:常规错误处理使用 error 返回;仅在真正特殊的情况下 panic → [go-defensive](../go-defensive/SKILL.md)
---
## 声明与初始化
- [ ] **分组相似的**:相关的 `var`/`const`/`type` 放在括号块中;不相关的分开 → [go-declarations](../go-declarations/SKILL.md)
- [ ] **var vs :=**:有意使用零值时用 `var`;显式赋值时用 `:=` → [go-declarations](../go-declarations/SKILL.md)
- [ ] **缩小作用域**:将声明移到使用位置附近;使用 if-init 限制变量作用域 → [go-declarations](../go-declarations/SKILL.md)
- [ ] **Struct 初始化**:始终使用字段名;省略零值字段;零值 struct 使用 `var` → [go-declarations](../go-declarations/SKILL.md)
- [ ] **使用 `any`**:新代码中优先使用 `any` 而非 `interface{}` → [go-declarations](../go-declarations/SKILL.md)
---
## 函数
- [ ] **文件排序**:类型 → 构造函数 → 导出方法 → 未导出方法 → 工具函数 → [go-functions](../go-functions/SKILL.md)
- [ ] **签名格式化**:换行时所有参数各占一行并带尾逗号 → [go-functions](../go-functions/SKILL.md)
- [ ] **裸参数**:为含义不明确的 bool/int 参数添加 `/* name */` 注释,或使用自定义类型 → [go-functions](../go-functions/SKILL.md)
- [ ] **Printf 命名**:接受格式字符串的函数以 `f` 结尾,以便 `go vet` 检查 → [go-functions](../go-functions/SKILL.md)
---
## 风格
- [ ] **行长度**:无硬性限制,但避免令人不适的长行;按语义断行,而非任意长度 → [go-style-core](../go-style-core/SKILL.md)
- [ ] **裸返回**:仅在短函数中使用;中/大函数使用显式返回 → [go-style-core](../go-style-core/SKILL.md)
- [ ] **传值**:不要仅为节省字节而使用指针;小的固定大小类型传 `string` 而非 `*string` → [go-performance](../go-performance/SKILL.md)
- [ ] **字符串拼接**:简单拼接用 `+`;格式化用 `fmt.Sprintf`;循环中用 `strings.Builder` → [go-performance](../go-performance/SKILL.md)
---
## 日志
- [ ] **使用 slog**:新代码使用 `log/slog`,不使用 `log` 或 `fmt.Println` 进行运维日志记录 → [go-logging](../go-logging/SKILL.md)
- [ ] **结构化字段**:日志消息使用静态字符串加键值属性,不使用 fmt.Sprintf → [go-logging](../go-logging/SKILL.md)
- [ ] **适当的级别**:Debug 用于开发者追踪,Info 用于重要事件,Warn 用于可恢复的问题,Error 用于故障 → [go-logging](../go-logging/SKILL.md)
- [ ] **日志中无敏感信息**:PII、凭证和令牌永远不记录在日志中 → [go-logging](../go-logging/SKILL.md)
---
## 导入
- [ ] **导入分组**:标准库优先,然后空行,再外部包 → [go-packages](../go-packages/SKILL.md)
- [ ] **导入重命名**:除非冲突否则避免重命名;冲突时重命名本地/项目特定的导入 → [go-packages](../go-packages/SKILL.md)
- [ ] **空白导入**:`import _ "pkg"` 仅在 main 包或测试中使用 → [go-packages](../go-packages/SKILL.md)
- [ ] **点导入**:仅在测试中用于解决循环依赖 → [go-packages](../go-packages/SKILL.md)
---
## 泛型
- [ ] **何时使用**:仅当多个类型共享相同逻辑且接口不足时 → [go-generics](../go-generics/SKILL.md)
- [ ] **类型别名**:使用定义创建新类型;别名仅用于包迁移 → [go-generics](../go-generics/SKILL.md)
---
## 测试
- [ ] **示例**:包含可运行的 `Example` 函数或演示用法的测试 → [go-documentation](../go-documentation/SKILL.md)
- [ ] **有用的测试失败信息**:消息包含出了什么错、输入、实际值和期望值;顺序为 `got != want` → [go-testing](../go-testing/SKILL.md)
- [ ] **TestMain**:仅当所有测试都需要带清理的公共设置时使用;优先使用作用域化的 helper → [go-testing](../go-testing/SKILL.md)
- [ ] **真实传输**:优先使用 `httptest.NewServer` + 真实客户端而非 mock HTTP → [go-testing](../go-testing/SKILL.md)
---
## 自动化检查
运行自动化预审查检查:
```bash
bash scripts/pre-review.sh ./... # 文本输出
bash scripts/pre-review.sh --json ./... # 结构化 JSON 输出
```
或手动:`gofmt -l <path> && go vet ./... && golangci-lint run ./...`
在进入上述清单之前修复所有问题。有关 linter 设置和配置,请参阅 [go-linting](../go-linting/SKILL.md)。
---
## 综合示例
> 在构建生产级 HTTP 服务器并希望验证代码是否正确应用了并发、错误处理、context、文档和命名规范时,阅读 [references/WEB-SERVER.md](references/WEB-SERVER.md)。
---
## 相关 Skill
- **风格基础**:在解决格式化争议或应用"清晰 > 简单 > 简洁"优先级时,请参阅 [go-style-core](../go-style-core/SKILL.md)
- **Linting 设置**:在配置 golangci-lint 或将自动化检查添加到 CI 时,请参阅 [go-linting](../go-linting/SKILL.md)
- **错误策略**:在审查错误包装、哨兵错误或 handle-once 模式时,请参阅 [go-error-handling](../go-error-handling/SKILL.md)
- **命名规范**:在评估标识符名称、接收器名称或包-符号重复时,请参阅 [go-naming](../go-naming/SKILL.md)
- **测试模式**:在审查表驱动结构、失败消息或 helper 使用的测试代码时,请参阅 [go-testing](../go-testing/SKILL.md)
- **并发安全**:在审查 goroutine 生命周期、channel 使用或互斥锁放置时,请参阅 [go-concurrency](../go-concurrency/SKILL.md)
- **日志实践**:在审查日志使用、结构化日志或 slog 配置时,请参阅 [go-logging](../go-logging/SKILL.md)
@@ -0,0 +1,23 @@
# Code Review: [PR Title]
## Summary
[Brief description of the changes]
## Findings
### Must Fix
- [ ] [file:line] Description of critical issue
### Should Fix
- [ ] [file:line] Description of recommended improvement
### Nits
- [ ] [file:line] Description of minor suggestion
## Automated Checks
- [ ] `gofmt -d .` — clean
- [ ] `go vet ./...` — clean
- [ ] `golangci-lint run` — clean
## Skills Applied
[List of go-* skills referenced during review]
@@ -0,0 +1,119 @@
# Web 服务器:Skill 的综合应用
本示例展示 Go skill 如何在真实的 HTTP 服务器中协同应用。每个部分
引用相关的 skill 以获取详细指导。
## 结构
```go
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"time"
)
// --- 接口(go-interfaces) ---
// Store 定义了数据访问边界。定义在消费方包中,
// 而非实现方包中。
type Store interface {
GetUser(ctx context.Context, id string) (*User, error)
}
// --- 类型与构造函数(go-naming、go-declarations) ---
// Server 处理用户 API 的 HTTP 请求。
type Server struct {
store Store
router *http.ServeMux
}
// NewServer 使用给定的依赖创建 Server。
// 调用者必须调用 Shutdown 来释放资源。
func NewServer(store Store) *Server {
s := &Server{store: store}
s.router = http.NewServeMux()
s.router.HandleFunc("GET /users/{id}", s.handleGetUser)
return s
}
// --- 错误处理(go-error-handling) ---
// 领域错误作为哨兵 —— 使用 errors.Is 进行检查。
var ErrNotFound = errors.New("not found")
// --- HTTP 处理器(go-control-flow、go-context、go-error-handling) ---
func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // go-context:从 request 派生
id := r.PathValue("id")
user, err := s.store.GetUser(ctx, id)
if err != nil {
if errors.Is(err, ErrNotFound) { // go-error-handling:errors.Is
http.Error(w, "user not found", http.StatusNotFound)
return // go-control-flow:提前返回
}
// HTTP 处理器是"记录或返回"规则的例外:在服务端记录详细信息,向客户端返回脱敏错误。
slog.Error("GetUser failed", "id", id, "err", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
// --- 优雅关闭(go-concurrency、go-defensive) ---
func main() {
store := NewDBStore(os.Getenv("DATABASE_URL"))
srv := NewServer(store)
httpSrv := &http.Server{
Addr: ":8080",
Handler: srv.router,
ReadTimeout: 5 * time.Second, // go-defensive:使用 time.Duration
WriteTimeout: 10 * time.Second,
}
// go-concurrency:goroutine 生命周期清晰
go func() {
sigCh := make(chan os.Signal, 1) // go-concurrency:channel 大小为 1
signal.Notify(sigCh, os.Interrupt)
<-sigCh
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() // go-defensive:defer 清理
httpSrv.Shutdown(ctx)
}()
slog.Info("starting server", "addr", httpSrv.Addr)
if err := httpSrv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
slog.Error("server error", "err", err)
os.Exit(1) // go-packages:仅在 main 中退出
}
}
```
## 应用的 Skill
| 领域 | Skill | 演示内容 |
|------|-------|----------|
| 接口在消费方 | [go-interfaces](../../go-interfaces/SKILL.md) | `Store` 在使用处定义 |
| 命名 | [go-naming](../../go-naming/SKILL.md) | MixedCaps、接收器缩写、清晰的函数名 |
| 错误处理 | [go-error-handling](../../go-error-handling/SKILL.md) | 哨兵错误、`errors.Is`、记录或返回 |
| Context | [go-context](../../go-context/SKILL.md) | 从 request 派生,逐层传递 |
| 控制流 | [go-control-flow](../../go-control-flow/SKILL.md) | 错误情况的提前返回 |
| 并发 | [go-concurrency](../../go-concurrency/SKILL.md) | 清晰的 goroutine 生命周期、channel 大小 |
| 防御性 | [go-defensive](../../go-defensive/SKILL.md) | `defer cancel()`、`time.Duration`、优雅关闭 |
| 包管理 | [go-packages](../../go-packages/SKILL.md) | 仅在 `main()` 中退出 |
| 日志 | [go-error-handling](../../go-error-handling/SKILL.md) | 结构化 slog,错误只处理一次 |
+246
View File
@@ -0,0 +1,246 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.0.0"
SCRIPT_NAME="$(basename "$0")"
usage() {
cat <<EOF
$SCRIPT_NAME v$VERSION — Run automated pre-review checks on Go code
USAGE
bash $SCRIPT_NAME [options] [path]
DESCRIPTION
Runs gofmt, go vet, and golangci-lint against the target path and
reports any findings. Use before manual code review to catch
mechanical issues early.
Exits 0 if all checks pass, 1 if issues found, 2 on error.
OPTIONS
-h, --help Show this help message
-v, --version Show version
--json Output results as JSON
--force Run even if golangci-lint is not installed (skip it)
--limit N Max items reported per section (0 = unlimited, default: 0)
ARGUMENTS
path Package pattern to check (default: ./...)
EXAMPLES
bash $SCRIPT_NAME
bash $SCRIPT_NAME ./pkg/...
bash $SCRIPT_NAME --json ./cmd/server/...
bash $SCRIPT_NAME --force ./...
bash $SCRIPT_NAME --json --limit 10 ./...
EOF
}
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\t'/\\t}"
s="${s//$'\r'/}"
s="${s//$'\n'/\\n}"
printf '%s' "$s"
}
JSON_OUTPUT=false
FORCE=false
LIMIT=0
TARGET=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-v|--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;;
--json) JSON_OUTPUT=true; shift ;;
--force) FORCE=true; shift ;;
--limit) LIMIT="${2:?error: --limit requires a number}"; shift 2 ;;
-*) echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;;
*) TARGET="$1"; shift ;;
esac
done
TARGET="${TARGET:-./...}"
if ! command -v go &>/dev/null; then
echo "error: go is not installed or not in PATH" >&2
exit 2
fi
if ! command -v gofmt &>/dev/null; then
echo "error: gofmt is not installed or not in PATH" >&2
exit 2
fi
GOFMT_STATUS="pass"
GOFMT_FINDINGS=()
GOFMT_DIR="${TARGET%%/...}"
GOFMT_DIR="${GOFMT_DIR:-.}"
UNFORMATTED=$(gofmt -l "$GOFMT_DIR" 2>&1) || true
if [[ -n "$UNFORMATTED" ]]; then
GOFMT_STATUS="fail"
while IFS= read -r f; do
[[ -n "$f" ]] && GOFMT_FINDINGS+=("$f")
done <<< "$UNFORMATTED"
fi
GOVET_STATUS="pass"
GOVET_OUTPUT=""
if ! GOVET_OUTPUT=$(go vet "$TARGET" 2>&1); then
GOVET_STATUS="fail"
fi
LINT_STATUS="skip"
LINT_OUTPUT=""
if command -v golangci-lint &>/dev/null; then
LINT_STATUS="pass"
if ! LINT_OUTPUT=$(golangci-lint run "$TARGET" 2>&1); then
LINT_STATUS="fail"
fi
elif ! $FORCE; then
echo "error: golangci-lint not installed (use --force to skip)" >&2
exit 2
fi
FAILED=0
[[ "$GOFMT_STATUS" == "fail" ]] && FAILED=1
[[ "$GOVET_STATUS" == "fail" ]] && FAILED=1
[[ "$LINT_STATUS" == "fail" ]] && FAILED=1
if $JSON_OUTPUT; then
GOFMT_TRUNCATED=false
GOFMT_DISPLAY=("${GOFMT_FINDINGS[@]+"${GOFMT_FINDINGS[@]}"}")
if [[ $LIMIT -gt 0 && ${#GOFMT_DISPLAY[@]} -gt $LIMIT ]]; then
GOFMT_DISPLAY=("${GOFMT_FINDINGS[@]:0:$LIMIT}")
GOFMT_TRUNCATED=true
fi
GOFMT_JSON="["
first=true
for f in "${GOFMT_DISPLAY[@]+"${GOFMT_DISPLAY[@]}"}"; do
$first || GOFMT_JSON+=","
first=false
GOFMT_JSON+="\"$(json_escape "$f")\""
done
GOFMT_JSON+="]"
GOVET_TRUNCATED=false
GOVET_DISPLAY="$GOVET_OUTPUT"
if [[ $LIMIT -gt 0 && -n "$GOVET_OUTPUT" ]]; then
GOVET_ARR=()
while IFS= read -r line; do
GOVET_ARR+=("$line")
done <<< "$GOVET_OUTPUT"
if [[ ${#GOVET_ARR[@]} -gt $LIMIT ]]; then
GOVET_DISPLAY=""
for (( i=0; i<LIMIT; i++ )); do
[[ -n "$GOVET_DISPLAY" ]] && GOVET_DISPLAY+=$'\n'
GOVET_DISPLAY+="${GOVET_ARR[$i]}"
done
GOVET_TRUNCATED=true
fi
fi
GOVET_ESC="$(json_escape "$GOVET_DISPLAY")"
LINT_TRUNCATED=false
LINT_DISPLAY="$LINT_OUTPUT"
if [[ $LIMIT -gt 0 && -n "$LINT_OUTPUT" ]]; then
LINT_ARR=()
while IFS= read -r line; do
LINT_ARR+=("$line")
done <<< "$LINT_OUTPUT"
if [[ ${#LINT_ARR[@]} -gt $LIMIT ]]; then
LINT_DISPLAY=""
for (( i=0; i<LIMIT; i++ )); do
[[ -n "$LINT_DISPLAY" ]] && LINT_DISPLAY+=$'\n'
LINT_DISPLAY+="${LINT_ARR[$i]}"
done
LINT_TRUNCATED=true
fi
fi
LINT_ESC="$(json_escape "$LINT_DISPLAY")"
GOFMT_TRUNC=""
$GOFMT_TRUNCATED && GOFMT_TRUNC=',"truncated":true'
GOVET_TRUNC=""
$GOVET_TRUNCATED && GOVET_TRUNC=',"truncated":true'
LINT_TRUNC=""
$LINT_TRUNCATED && LINT_TRUNC=',"truncated":true'
cat <<EOF
{"gofmt":{"status":"$GOFMT_STATUS","files":$GOFMT_JSON$GOFMT_TRUNC},"govet":{"status":"$GOVET_STATUS","output":"$GOVET_ESC"$GOVET_TRUNC},"golangci_lint":{"status":"$LINT_STATUS","output":"$LINT_ESC"$LINT_TRUNC},"passed":$( [[ $FAILED -eq 0 ]] && echo true || echo false )}
EOF
else
echo "=== gofmt ==="
if [[ "$GOFMT_STATUS" == "fail" ]]; then
echo "Unformatted files:"
GOFMT_COUNT=0
for f in "${GOFMT_FINDINGS[@]}"; do
GOFMT_COUNT=$((GOFMT_COUNT + 1))
if [[ $LIMIT -gt 0 && $GOFMT_COUNT -gt $LIMIT ]]; then
echo " ... ($(( ${#GOFMT_FINDINGS[@]} - LIMIT )) more items truncated)"
break
fi
echo " $f"
done
else
echo "OK"
fi
echo ""
echo "=== go vet ==="
if [[ "$GOVET_STATUS" == "fail" ]]; then
if [[ $LIMIT -gt 0 ]]; then
GOVET_ARR=()
while IFS= read -r line; do
GOVET_ARR+=("$line")
done <<< "$GOVET_OUTPUT"
for (( i=0; i<${#GOVET_ARR[@]} && i<LIMIT; i++ )); do
echo "${GOVET_ARR[$i]}"
done
if [[ ${#GOVET_ARR[@]} -gt $LIMIT ]]; then
echo "... ($(( ${#GOVET_ARR[@]} - LIMIT )) more items truncated)"
fi
else
echo "$GOVET_OUTPUT"
fi
else
echo "OK"
fi
echo ""
echo "=== golangci-lint ==="
if [[ "$LINT_STATUS" == "skip" ]]; then
echo "Skipped (not installed)"
elif [[ "$LINT_STATUS" == "fail" ]]; then
if [[ $LIMIT -gt 0 ]]; then
LINT_ARR=()
while IFS= read -r line; do
LINT_ARR+=("$line")
done <<< "$LINT_OUTPUT"
for (( i=0; i<${#LINT_ARR[@]} && i<LIMIT; i++ )); do
echo "${LINT_ARR[$i]}"
done
if [[ ${#LINT_ARR[@]} -gt $LIMIT ]]; then
echo "... ($(( ${#LINT_ARR[@]} - LIMIT )) more items truncated)"
fi
else
echo "$LINT_OUTPUT"
fi
else
echo "OK"
fi
echo ""
if [[ $FAILED -eq 1 ]]; then
echo "Pre-review checks FAILED — fix issues before manual review."
else
echo "All pre-review checks passed."
fi
fi
exit $FAILED