mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 10:29:48 +08:00
171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package semantic
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/larksuite/cli/internal/qualitygate/facts"
|
|
"github.com/larksuite/cli/internal/vfs"
|
|
)
|
|
|
|
var (
|
|
ErrReviewerUnavailable = errors.New("semantic reviewer is not configured")
|
|
ErrReviewerConfiguration = errors.New("semantic reviewer configuration is invalid")
|
|
)
|
|
|
|
func LoadOrReviewWithConfig(ctx context.Context, f facts.Facts, reviewPath string, cfg ModelConfig) (Review, error) {
|
|
if reviewPath == "" {
|
|
client, ok, err := FromEnvWithConfig(cfg)
|
|
if err != nil {
|
|
return Review{}, err
|
|
}
|
|
if !ok {
|
|
return Review{}, ErrReviewerUnavailable
|
|
}
|
|
return client.Review(ctx, f)
|
|
}
|
|
data, err := vfs.ReadFile(reviewPath)
|
|
if err != nil {
|
|
return Review{}, err
|
|
}
|
|
return DecodeReview(strings.NewReader(string(data)))
|
|
}
|
|
|
|
func SkippedDecision(err error) Decision {
|
|
return Decision{
|
|
Skipped: true,
|
|
SystemWarnings: []SystemWarning{{
|
|
Severity: "minor",
|
|
Message: fmt.Sprintf("semantic review skipped: %v", err),
|
|
SuggestedAction: "configure semantic review credentials and model when enabling model-based review",
|
|
}},
|
|
}
|
|
}
|
|
|
|
func DegradedDecision(err error) Decision {
|
|
return Decision{
|
|
Degraded: true,
|
|
SystemWarnings: []SystemWarning{{
|
|
Severity: "minor",
|
|
Message: fmt.Sprintf("semantic review degraded: %v", err),
|
|
SuggestedAction: "inspect deterministic quality-gate diagnostics",
|
|
}},
|
|
}
|
|
}
|
|
|
|
func InfrastructureFailureDecision(err error) Decision {
|
|
return Decision{
|
|
Degraded: true,
|
|
InfrastructureFailure: true,
|
|
SystemWarnings: []SystemWarning{{
|
|
Severity: "critical",
|
|
Message: fmt.Sprintf("semantic review infrastructure failure: %v", err),
|
|
SuggestedAction: "inspect semantic-review workflow logs and quality-gate configuration",
|
|
}},
|
|
}
|
|
}
|
|
|
|
func WriteDecision(path string, decision Decision) error {
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
data, err := json.MarshalIndent(decision, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data = append(data, '\n')
|
|
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
return vfs.WriteFile(path, data, 0o644)
|
|
}
|
|
|
|
func WriteMarkdown(path string, decision Decision) error {
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
body := Markdown(decision)
|
|
if err := vfs.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
return vfs.WriteFile(path, []byte(body), 0o644)
|
|
}
|
|
|
|
func Markdown(decision Decision) string {
|
|
var b strings.Builder
|
|
b.WriteString("## Semantic Review\n\n")
|
|
if decision.Skipped {
|
|
b.WriteString("Semantic review skipped; deterministic quality-gate results remain authoritative.\n\n")
|
|
}
|
|
if decision.Degraded {
|
|
b.WriteString("Semantic review degraded; deterministic quality-gate results remain authoritative.\n\n")
|
|
}
|
|
if len(decision.Blockers) == 0 {
|
|
b.WriteString("No semantic blockers.\n\n")
|
|
} else {
|
|
b.WriteString("### Blockers\n\n")
|
|
for _, finding := range decision.Blockers {
|
|
b.WriteString("- ")
|
|
b.WriteString(markdownFindingText(finding.Message))
|
|
b.WriteByte('\n')
|
|
}
|
|
b.WriteByte('\n')
|
|
}
|
|
if len(decision.Warnings) > 0 {
|
|
b.WriteString("### Warnings\n\n")
|
|
for _, finding := range decision.Warnings {
|
|
b.WriteString("- ")
|
|
b.WriteString(markdownFindingText(finding.Message))
|
|
b.WriteByte('\n')
|
|
}
|
|
}
|
|
if len(decision.SystemWarnings) > 0 {
|
|
b.WriteString("\n### System Warnings\n\n")
|
|
for _, warning := range decision.SystemWarnings {
|
|
b.WriteString("- ")
|
|
b.WriteString(markdownFindingText(warning.Message))
|
|
if warning.SuggestedAction != "" {
|
|
b.WriteString(" ")
|
|
b.WriteString(markdownFindingText(warning.SuggestedAction))
|
|
}
|
|
b.WriteByte('\n')
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func markdownFindingText(raw string) string {
|
|
var b strings.Builder
|
|
for _, r := range raw {
|
|
switch {
|
|
case r == '\n' || r == '\r' || r == '\t':
|
|
b.WriteByte(' ')
|
|
case unicode.IsControl(r):
|
|
continue
|
|
case r == '@':
|
|
b.WriteString("@\u200b")
|
|
case r == '<':
|
|
b.WriteString("<")
|
|
case r == '>':
|
|
b.WriteString(">")
|
|
case strings.ContainsRune("\\`*_{}[]()#+-|!", r):
|
|
b.WriteByte('\\')
|
|
b.WriteRune(r)
|
|
default:
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
text := strings.Join(strings.Fields(b.String()), " ")
|
|
text = strings.ReplaceAll(text, "https://", "https[:]//")
|
|
text = strings.ReplaceAll(text, "http://", "http[:]//")
|
|
return text
|
|
}
|