Merge branch 'feat/ml-integration' into dev
Some checks are pending
Security Scan / Go Vulnerability Check (push) Waiting to run
Security Scan / Secret Detection (push) Waiting to run
Security Scan / Dependency & Config Scan (push) Waiting to run

# Conflicts:
#	.gh-actions/ISSUE_TEMPLATE/config.yml
#	.gh-actions/workflows/alpha-release-manual.yml
#	.gh-actions/workflows/alpha-release-push.yml
#	.gh-actions/workflows/alpha-release.yml
#	.gh-actions/workflows/bugseti-release.yml
#	.gh-actions/workflows/ci-manual.yml
#	.gh-actions/workflows/ci-pull-request.yml
#	.gh-actions/workflows/ci-push.yml
#	.gh-actions/workflows/ci.yml
#	.gh-actions/workflows/coverage-manual.yml
#	.gh-actions/workflows/coverage-pull-request.yml
#	.gh-actions/workflows/coverage-push.yml
#	.gh-actions/workflows/coverage.yml
#	.gh-actions/workflows/release.yml
#	cmd/bugseti/go.mod
#	cmd/bugseti/workspace.go
#	go.sum
#	internal/bugseti/submit.go
#	internal/bugseti/updater/go.mod
#	internal/cmd/ml/cmd_ml.go
#	internal/core-ide/go.mod
#	internal/variants/full.go
#	pkg/ml/db.go
This commit is contained in:
Snider 2026-02-16 06:13:40 +00:00
commit 48d385279b
10 changed files with 1257 additions and 1706 deletions

View file

@ -7,6 +7,9 @@ require (
forge.lthn.ai/core/cli/internal/bugseti v0.0.0
forge.lthn.ai/core/cli/internal/bugseti/updater v0.0.0
github.com/Snider/Borg v0.2.0
forge.lthn.ai/core/cli v0.0.0
forge.lthn.ai/core/cli/internal/bugseti v0.0.0
forge.lthn.ai/core/cli/internal/bugseti/updater v0.0.0
github.com/wailsapp/wails/v3 v3.0.0-alpha.64
)

BIN
core-ide Executable file

Binary file not shown.

View file

@ -313,7 +313,7 @@ func (s *SubmitService) generatePRBody(issue *Issue) string {
body.WriteString("<!-- Describe how you tested your changes -->\n\n")
body.WriteString("---\n\n")
body.WriteString("*Submitted via [BugSETI](https://bugseti.app) - Distributed Bug Fixing*\n")
body.WriteString("*Submitted via [BugSETI](https://forge.lthn.ai/core/cli) - Distributed Bug Fixing*\n")
return body.String()
}

View file

@ -5495,17 +5495,6 @@
"node": ">= 0.4"
}
},
"node_modules/hono": {
"version": "4.11.7",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/hosted-git-info": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz",

View file

@ -49,6 +49,8 @@ require (
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3
forge.lthn.ai/core/cli v0.0.0
forge.lthn.ai/core/cli-gui v0.0.0
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect

560
pkg/devkit/devkit.go Normal file
View file

@ -0,0 +1,560 @@
// Package devkit provides a developer toolkit for common automation commands.
// Designed by Gemini 3 Pro (Hypnos) + Claude Opus (Charon), signed LEK-1 | lthn.ai | EUPL-1.2
package devkit
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
// --- Code Quality ---
// Finding represents a single issue found by a linting tool.
type Finding struct {
File string
Line int
Message string
Tool string
}
// CoverageReport holds the test coverage percentage for a package.
type CoverageReport struct {
Package string
Percentage float64
}
// RaceCondition represents a data race detected by the Go race detector.
type RaceCondition struct {
File string
Line int
Desc string
}
// TODO represents a tracked code comment like TODO, FIXME, or HACK.
type TODO struct {
File string
Line int
Type string
Message string
}
// --- Security ---
// Vulnerability represents a dependency vulnerability.
type Vulnerability struct {
ID string
Package string
Version string
Description string
}
// SecretLeak represents a potential secret found in the codebase.
type SecretLeak struct {
File string
Line int
RuleID string
Match string
}
// PermIssue represents a file permission issue.
type PermIssue struct {
File string
Permission string
Issue string
}
// --- Git Operations ---
// DiffSummary provides a summary of changes.
type DiffSummary struct {
FilesChanged int
Insertions int
Deletions int
}
// Commit represents a single git commit.
type Commit struct {
Hash string
Author string
Date time.Time
Message string
}
// --- Build & Dependencies ---
// BuildResult holds the outcome of a single build target.
type BuildResult struct {
Target string
Path string
Error error
}
// Graph represents a dependency graph.
type Graph struct {
Nodes []string
Edges map[string][]string
}
// --- Metrics ---
// ComplexFunc represents a function with its cyclomatic complexity score.
type ComplexFunc struct {
Package string
FuncName string
File string
Line int
Score int
}
// Toolkit wraps common dev automation commands into structured Go APIs.
type Toolkit struct {
Dir string // Working directory for commands
}
// New creates a Toolkit rooted at the given directory.
func New(dir string) *Toolkit {
return &Toolkit{Dir: dir}
}
// Run executes a command and captures stdout, stderr, and exit code.
func (t *Toolkit) Run(name string, args ...string) (stdout, stderr string, exitCode int, err error) {
cmd := exec.Command(name, args...)
cmd.Dir = t.Dir
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
err = cmd.Run()
stdout = stdoutBuf.String()
stderr = stderrBuf.String()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
} else {
exitCode = -1
}
}
return
}
// FindTODOs greps for TODO/FIXME/HACK comments within a directory.
func (t *Toolkit) FindTODOs(dir string) ([]TODO, error) {
pattern := `\b(TODO|FIXME|HACK)\b(\(.*\))?:`
stdout, stderr, exitCode, err := t.Run("git", "grep", "--line-number", "-E", pattern, "--", dir)
if exitCode == 1 && stdout == "" {
return nil, nil
}
if err != nil && exitCode != 1 {
return nil, fmt.Errorf("git grep failed (exit %d): %s\n%s", exitCode, err, stderr)
}
var todos []TODO
re := regexp.MustCompile(pattern)
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
if line == "" {
continue
}
parts := strings.SplitN(line, ":", 3)
if len(parts) < 3 {
continue
}
lineNum, _ := strconv.Atoi(parts[1])
match := re.FindStringSubmatch(parts[2])
todoType := ""
if len(match) > 1 {
todoType = match[1]
}
msg := strings.TrimSpace(re.Split(parts[2], 2)[1])
todos = append(todos, TODO{
File: parts[0],
Line: lineNum,
Type: todoType,
Message: msg,
})
}
return todos, nil
}
// AuditDeps runs govulncheck to find dependency vulnerabilities.
func (t *Toolkit) AuditDeps() ([]Vulnerability, error) {
stdout, stderr, exitCode, err := t.Run("govulncheck", "./...")
if err != nil && exitCode != 0 && !strings.Contains(stdout, "Vulnerability") {
return nil, fmt.Errorf("govulncheck failed (exit %d): %s\n%s", exitCode, err, stderr)
}
var vulns []Vulnerability
scanner := bufio.NewScanner(strings.NewReader(stdout))
var cur Vulnerability
inBlock := false
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Vulnerability #") {
if cur.ID != "" {
vulns = append(vulns, cur)
}
fields := strings.Fields(line)
cur = Vulnerability{}
if len(fields) > 1 {
cur.ID = fields[1]
}
inBlock = true
} else if inBlock {
switch {
case strings.Contains(line, "Package:"):
cur.Package = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
case strings.Contains(line, "Found in version:"):
cur.Version = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
case line == "":
if cur.ID != "" {
vulns = append(vulns, cur)
cur = Vulnerability{}
}
inBlock = false
default:
if !strings.HasPrefix(line, " ") && cur.Description == "" {
cur.Description = strings.TrimSpace(line)
}
}
}
}
if cur.ID != "" {
vulns = append(vulns, cur)
}
return vulns, nil
}
// DiffStat returns a summary of uncommitted changes.
func (t *Toolkit) DiffStat() (DiffSummary, error) {
stdout, stderr, exitCode, err := t.Run("git", "diff", "--stat")
if err != nil && exitCode != 0 {
return DiffSummary{}, fmt.Errorf("git diff failed (exit %d): %s\n%s", exitCode, err, stderr)
}
var s DiffSummary
lines := strings.Split(strings.TrimSpace(stdout), "\n")
if len(lines) == 0 || lines[0] == "" {
return s, nil
}
last := lines[len(lines)-1]
for _, part := range strings.Split(last, ",") {
part = strings.TrimSpace(part)
fields := strings.Fields(part)
if len(fields) < 2 {
continue
}
val, _ := strconv.Atoi(fields[0])
switch {
case strings.Contains(part, "file"):
s.FilesChanged = val
case strings.Contains(part, "insertion"):
s.Insertions = val
case strings.Contains(part, "deletion"):
s.Deletions = val
}
}
return s, nil
}
// UncommittedFiles returns paths of files with uncommitted changes.
func (t *Toolkit) UncommittedFiles() ([]string, error) {
stdout, stderr, exitCode, err := t.Run("git", "status", "--porcelain")
if err != nil && exitCode != 0 {
return nil, fmt.Errorf("git status failed: %s\n%s", err, stderr)
}
var files []string
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
if len(line) > 3 {
files = append(files, strings.TrimSpace(line[3:]))
}
}
return files, nil
}
// Lint runs go vet on the given package pattern.
func (t *Toolkit) Lint(pkg string) ([]Finding, error) {
_, stderr, exitCode, err := t.Run("go", "vet", pkg)
if exitCode == 0 {
return nil, nil
}
if err != nil && exitCode != 2 {
return nil, fmt.Errorf("go vet failed: %w", err)
}
var findings []Finding
for _, line := range strings.Split(strings.TrimSpace(stderr), "\n") {
if line == "" {
continue
}
parts := strings.SplitN(line, ":", 4)
if len(parts) < 4 {
continue
}
lineNum, _ := strconv.Atoi(parts[1])
findings = append(findings, Finding{
File: parts[0],
Line: lineNum,
Message: strings.TrimSpace(parts[3]),
Tool: "go vet",
})
}
return findings, nil
}
// ScanSecrets runs gitleaks to find potential secret leaks.
func (t *Toolkit) ScanSecrets(dir string) ([]SecretLeak, error) {
stdout, _, exitCode, err := t.Run("gitleaks", "detect", "--source", dir, "--report-format", "csv", "--no-git")
if exitCode == 0 {
return nil, nil
}
if err != nil && exitCode != 1 {
return nil, fmt.Errorf("gitleaks failed: %w", err)
}
var leaks []SecretLeak
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
if line == "" || strings.HasPrefix(line, "RuleID") {
continue
}
parts := strings.SplitN(line, ",", 4)
if len(parts) < 4 {
continue
}
lineNum, _ := strconv.Atoi(parts[2])
leaks = append(leaks, SecretLeak{
RuleID: parts[0],
File: parts[1],
Line: lineNum,
Match: parts[3],
})
}
return leaks, nil
}
// ModTidy runs go mod tidy.
func (t *Toolkit) ModTidy() error {
_, stderr, exitCode, err := t.Run("go", "mod", "tidy")
if err != nil && exitCode != 0 {
return fmt.Errorf("go mod tidy failed: %s", stderr)
}
return nil
}
// Build compiles the given targets.
func (t *Toolkit) Build(targets ...string) ([]BuildResult, error) {
var results []BuildResult
for _, target := range targets {
_, stderr, _, err := t.Run("go", "build", "-o", "/dev/null", target)
r := BuildResult{Target: target}
if err != nil {
r.Error = fmt.Errorf("%s", strings.TrimSpace(stderr))
}
results = append(results, r)
}
return results, nil
}
// TestCount returns the number of test functions in a package.
func (t *Toolkit) TestCount(pkg string) (int, error) {
stdout, stderr, exitCode, err := t.Run("go", "test", "-list", ".*", pkg)
if err != nil && exitCode != 0 {
return 0, fmt.Errorf("go test -list failed: %s\n%s", err, stderr)
}
count := 0
for _, line := range strings.Split(strings.TrimSpace(stdout), "\n") {
if strings.HasPrefix(line, "Test") || strings.HasPrefix(line, "Benchmark") {
count++
}
}
return count, nil
}
// Coverage runs go test -cover and parses per-package coverage percentages.
func (t *Toolkit) Coverage(pkg string) ([]CoverageReport, error) {
if pkg == "" {
pkg = "./..."
}
stdout, stderr, exitCode, err := t.Run("go", "test", "-cover", pkg)
if err != nil && exitCode != 0 && !strings.Contains(stdout, "coverage:") {
return nil, fmt.Errorf("go test -cover failed (exit %d): %s\n%s", exitCode, err, stderr)
}
var reports []CoverageReport
re := regexp.MustCompile(`ok\s+(\S+)\s+.*coverage:\s+([\d.]+)%`)
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
matches := re.FindStringSubmatch(scanner.Text())
if len(matches) == 3 {
pct, _ := strconv.ParseFloat(matches[2], 64)
reports = append(reports, CoverageReport{
Package: matches[1],
Percentage: pct,
})
}
}
return reports, nil
}
// RaceDetect runs go test -race and parses data race warnings.
func (t *Toolkit) RaceDetect(pkg string) ([]RaceCondition, error) {
if pkg == "" {
pkg = "./..."
}
_, stderr, _, err := t.Run("go", "test", "-race", pkg)
if err != nil && !strings.Contains(stderr, "WARNING: DATA RACE") {
return nil, fmt.Errorf("go test -race failed: %w", err)
}
var races []RaceCondition
lines := strings.Split(stderr, "\n")
reFile := regexp.MustCompile(`\s+(.*\.go):(\d+)`)
for i, line := range lines {
if strings.Contains(line, "WARNING: DATA RACE") {
rc := RaceCondition{Desc: "Data race detected"}
for j := i + 1; j < len(lines) && j < i+15; j++ {
if match := reFile.FindStringSubmatch(lines[j]); len(match) == 3 {
rc.File = strings.TrimSpace(match[1])
rc.Line, _ = strconv.Atoi(match[2])
break
}
}
races = append(races, rc)
}
}
return races, nil
}
// Complexity runs gocyclo and returns functions exceeding the threshold.
func (t *Toolkit) Complexity(threshold int) ([]ComplexFunc, error) {
stdout, stderr, exitCode, err := t.Run("gocyclo", "-over", strconv.Itoa(threshold), ".")
if err != nil && exitCode == -1 {
return nil, fmt.Errorf("gocyclo not available: %s\n%s", err, stderr)
}
var funcs []ComplexFunc
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 4 {
continue
}
score, _ := strconv.Atoi(fields[0])
fileParts := strings.Split(fields[3], ":")
line := 0
if len(fileParts) > 1 {
line, _ = strconv.Atoi(fileParts[1])
}
funcs = append(funcs, ComplexFunc{
Score: score,
Package: fields[1],
FuncName: fields[2],
File: fileParts[0],
Line: line,
})
}
return funcs, nil
}
// DepGraph runs go mod graph and builds a dependency graph.
func (t *Toolkit) DepGraph(pkg string) (*Graph, error) {
stdout, stderr, exitCode, err := t.Run("go", "mod", "graph")
if err != nil && exitCode != 0 {
return nil, fmt.Errorf("go mod graph failed (exit %d): %s\n%s", exitCode, err, stderr)
}
graph := &Graph{Edges: make(map[string][]string)}
nodes := make(map[string]struct{})
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
parts := strings.Fields(scanner.Text())
if len(parts) >= 2 {
src, dst := parts[0], parts[1]
graph.Edges[src] = append(graph.Edges[src], dst)
nodes[src] = struct{}{}
nodes[dst] = struct{}{}
}
}
for node := range nodes {
graph.Nodes = append(graph.Nodes, node)
}
return graph, nil
}
// GitLog returns the last n commits from git history.
func (t *Toolkit) GitLog(n int) ([]Commit, error) {
stdout, stderr, exitCode, err := t.Run("git", "log", fmt.Sprintf("-n%d", n), "--format=%H|%an|%aI|%s")
if err != nil && exitCode != 0 {
return nil, fmt.Errorf("git log failed (exit %d): %s\n%s", exitCode, err, stderr)
}
var commits []Commit
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
parts := strings.SplitN(scanner.Text(), "|", 4)
if len(parts) < 4 {
continue
}
date, _ := time.Parse(time.RFC3339, parts[2])
commits = append(commits, Commit{
Hash: parts[0],
Author: parts[1],
Date: date,
Message: parts[3],
})
}
return commits, nil
}
// CheckPerms walks a directory and flags files with overly permissive modes.
func (t *Toolkit) CheckPerms(dir string) ([]PermIssue, error) {
var issues []PermIssue
err := filepath.Walk(filepath.Join(t.Dir, dir), func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.IsDir() {
return nil
}
mode := info.Mode().Perm()
if mode&0o002 != 0 {
issues = append(issues, PermIssue{
File: path,
Permission: fmt.Sprintf("%04o", mode),
Issue: "World-writable",
})
} else if mode&0o020 != 0 && mode&0o002 != 0 {
issues = append(issues, PermIssue{
File: path,
Permission: fmt.Sprintf("%04o", mode),
Issue: "Group and world-writable",
})
}
return nil
})
if err != nil {
return nil, fmt.Errorf("walk failed: %w", err)
}
return issues, nil
}
// LEK-1 | lthn.ai | EUPL-1.2

270
pkg/devkit/devkit_test.go Normal file
View file

@ -0,0 +1,270 @@
// Designed by Gemini 3 Pro (Hypnos) + Claude Opus (Charon), signed LEK-1 | lthn.ai | EUPL-1.2
package devkit
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"
)
// setupMockCmd creates a shell script in a temp dir that echoes predetermined
// content, and prepends that dir to PATH so Run() picks it up.
func setupMockCmd(t *testing.T, name, content string) {
t.Helper()
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, name)
script := fmt.Sprintf("#!/bin/sh\ncat <<'MOCK_EOF'\n%s\nMOCK_EOF\n", content)
if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil {
t.Fatalf("failed to write mock command %s: %v", name, err)
}
oldPath := os.Getenv("PATH")
t.Setenv("PATH", tmpDir+string(os.PathListSeparator)+oldPath)
}
// setupMockCmdExit creates a mock that echoes to stdout/stderr and exits with a code.
func setupMockCmdExit(t *testing.T, name, stdout, stderr string, exitCode int) {
t.Helper()
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, name)
script := fmt.Sprintf("#!/bin/sh\ncat <<'MOCK_EOF'\n%s\nMOCK_EOF\ncat <<'MOCK_ERR' >&2\n%s\nMOCK_ERR\nexit %d\n", stdout, stderr, exitCode)
if err := os.WriteFile(scriptPath, []byte(script), 0755); err != nil {
t.Fatalf("failed to write mock command %s: %v", name, err)
}
oldPath := os.Getenv("PATH")
t.Setenv("PATH", tmpDir+string(os.PathListSeparator)+oldPath)
}
func TestCoverage_Good(t *testing.T) {
output := `? example.com/skipped [no test files]
ok example.com/pkg1 0.5s coverage: 85.0% of statements
ok example.com/pkg2 0.2s coverage: 100.0% of statements`
setupMockCmd(t, "go", output)
tk := New(t.TempDir())
reports, err := tk.Coverage("./...")
if err != nil {
t.Fatalf("Coverage failed: %v", err)
}
if len(reports) != 2 {
t.Fatalf("expected 2 reports, got %d", len(reports))
}
if reports[0].Package != "example.com/pkg1" || reports[0].Percentage != 85.0 {
t.Errorf("report 0: want pkg1@85%%, got %s@%.1f%%", reports[0].Package, reports[0].Percentage)
}
if reports[1].Package != "example.com/pkg2" || reports[1].Percentage != 100.0 {
t.Errorf("report 1: want pkg2@100%%, got %s@%.1f%%", reports[1].Package, reports[1].Percentage)
}
}
func TestCoverage_Bad(t *testing.T) {
// No coverage lines in output
setupMockCmd(t, "go", "FAIL\texample.com/broken [build failed]")
tk := New(t.TempDir())
reports, err := tk.Coverage("./...")
if err != nil {
t.Fatalf("Coverage should not error on partial output: %v", err)
}
if len(reports) != 0 {
t.Errorf("expected 0 reports from failed build, got %d", len(reports))
}
}
func TestGitLog_Good(t *testing.T) {
now := time.Now().Truncate(time.Second)
nowStr := now.Format(time.RFC3339)
output := fmt.Sprintf("abc123|Alice|%s|Fix the bug\ndef456|Bob|%s|Add feature", nowStr, nowStr)
setupMockCmd(t, "git", output)
tk := New(t.TempDir())
commits, err := tk.GitLog(2)
if err != nil {
t.Fatalf("GitLog failed: %v", err)
}
if len(commits) != 2 {
t.Fatalf("expected 2 commits, got %d", len(commits))
}
if commits[0].Hash != "abc123" {
t.Errorf("hash: want abc123, got %s", commits[0].Hash)
}
if commits[0].Author != "Alice" {
t.Errorf("author: want Alice, got %s", commits[0].Author)
}
if commits[0].Message != "Fix the bug" {
t.Errorf("message: want 'Fix the bug', got %q", commits[0].Message)
}
if !commits[0].Date.Equal(now) {
t.Errorf("date: want %v, got %v", now, commits[0].Date)
}
}
func TestGitLog_Bad(t *testing.T) {
// Malformed lines should be skipped
setupMockCmd(t, "git", "incomplete|line\nabc|Bob|2025-01-01T00:00:00Z|Good commit")
tk := New(t.TempDir())
commits, err := tk.GitLog(5)
if err != nil {
t.Fatalf("GitLog failed: %v", err)
}
if len(commits) != 1 {
t.Errorf("expected 1 valid commit (skip malformed), got %d", len(commits))
}
}
func TestComplexity_Good(t *testing.T) {
output := "15 main ComplexFunc file.go:10:1\n20 pkg VeryComplex other.go:50:1"
setupMockCmd(t, "gocyclo", output)
tk := New(t.TempDir())
funcs, err := tk.Complexity(10)
if err != nil {
t.Fatalf("Complexity failed: %v", err)
}
if len(funcs) != 2 {
t.Fatalf("expected 2 funcs, got %d", len(funcs))
}
if funcs[0].Score != 15 || funcs[0].FuncName != "ComplexFunc" || funcs[0].File != "file.go" || funcs[0].Line != 10 {
t.Errorf("func 0: unexpected %+v", funcs[0])
}
if funcs[1].Score != 20 || funcs[1].Package != "pkg" {
t.Errorf("func 1: unexpected %+v", funcs[1])
}
}
func TestComplexity_Bad(t *testing.T) {
// No functions above threshold = empty output
setupMockCmd(t, "gocyclo", "")
tk := New(t.TempDir())
funcs, err := tk.Complexity(50)
if err != nil {
t.Fatalf("Complexity should not error on empty output: %v", err)
}
if len(funcs) != 0 {
t.Errorf("expected 0 funcs, got %d", len(funcs))
}
}
func TestDepGraph_Good(t *testing.T) {
output := "modA@v1 modB@v2\nmodA@v1 modC@v3\nmodB@v2 modD@v1"
setupMockCmd(t, "go", output)
tk := New(t.TempDir())
graph, err := tk.DepGraph("./...")
if err != nil {
t.Fatalf("DepGraph failed: %v", err)
}
if len(graph.Nodes) != 4 {
t.Errorf("expected 4 nodes, got %d: %v", len(graph.Nodes), graph.Nodes)
}
edgesA := graph.Edges["modA@v1"]
if len(edgesA) != 2 {
t.Errorf("expected 2 edges from modA@v1, got %d", len(edgesA))
}
}
func TestRaceDetect_Good(t *testing.T) {
// No races = clean run
setupMockCmd(t, "go", "ok\texample.com/safe\t0.1s")
tk := New(t.TempDir())
races, err := tk.RaceDetect("./...")
if err != nil {
t.Fatalf("RaceDetect failed on clean run: %v", err)
}
if len(races) != 0 {
t.Errorf("expected 0 races, got %d", len(races))
}
}
func TestRaceDetect_Bad(t *testing.T) {
stderrOut := `WARNING: DATA RACE
Read at 0x00c000123456 by goroutine 7:
/home/user/project/main.go:42
Previous write at 0x00c000123456 by goroutine 6:
/home/user/project/main.go:38`
setupMockCmdExit(t, "go", "", stderrOut, 1)
tk := New(t.TempDir())
races, err := tk.RaceDetect("./...")
if err != nil {
t.Fatalf("RaceDetect should parse races, not error: %v", err)
}
if len(races) != 1 {
t.Fatalf("expected 1 race, got %d", len(races))
}
if races[0].File != "/home/user/project/main.go" || races[0].Line != 42 {
t.Errorf("race: unexpected %+v", races[0])
}
}
func TestDiffStat_Good(t *testing.T) {
output := ` file1.go | 10 +++++++---
file2.go | 5 +++++
2 files changed, 12 insertions(+), 3 deletions(-)`
setupMockCmd(t, "git", output)
tk := New(t.TempDir())
s, err := tk.DiffStat()
if err != nil {
t.Fatalf("DiffStat failed: %v", err)
}
if s.FilesChanged != 2 {
t.Errorf("files: want 2, got %d", s.FilesChanged)
}
if s.Insertions != 12 {
t.Errorf("insertions: want 12, got %d", s.Insertions)
}
if s.Deletions != 3 {
t.Errorf("deletions: want 3, got %d", s.Deletions)
}
}
func TestCheckPerms_Good(t *testing.T) {
dir := t.TempDir()
// Create a world-writable file
badFile := filepath.Join(dir, "bad.txt")
if err := os.WriteFile(badFile, []byte("test"), 0644); err != nil {
t.Fatal(err)
}
if err := os.Chmod(badFile, 0666); err != nil {
t.Fatal(err)
}
// Create a safe file
goodFile := filepath.Join(dir, "good.txt")
if err := os.WriteFile(goodFile, []byte("test"), 0644); err != nil {
t.Fatal(err)
}
tk := New("/")
issues, err := tk.CheckPerms(dir)
if err != nil {
t.Fatalf("CheckPerms failed: %v", err)
}
if len(issues) != 1 {
t.Fatalf("expected 1 issue (world-writable), got %d", len(issues))
}
if issues[0].Issue != "World-writable" {
t.Errorf("issue: want 'World-writable', got %q", issues[0].Issue)
}
}
func TestNew(t *testing.T) {
tk := New("/tmp")
if tk.Dir != "/tmp" {
t.Errorf("Dir: want /tmp, got %s", tk.Dir)
}
}
// LEK-1 | lthn.ai | EUPL-1.2

View file

@ -1146,277 +1146,129 @@
"error.gh_not_found": "'gh' CLI not found. Install from https://cli.github.com/",
"error.registry_not_found": "No repos.yaml found",
"error.repo_not_found": "Repository '{{.Name}}' not found",
"gram.article.definite": "the",
"gram.article.definite.feminine": "",
"gram.article.definite.masculine": "",
"gram.article.definite.neuter": "",
"gram.article.indefinite.default": "a",
"gram.article.indefinite.feminine": "",
"gram.article.indefinite.masculine": "",
"gram.article.indefinite.neuter": "",
"gram.article.indefinite.vowel": "an",
"gram.noun.artifact.one": "artifact",
"gram.noun.artifact.other": "artifacts",
"gram.noun.branch.gender": "",
"gram.noun.branch.one": "branch",
"gram.noun.branch.other": "branches",
"gram.noun.category.one": "category",
"gram.noun.category.other": "categories",
"gram.noun.change.gender": "",
"gram.noun.change.one": "change",
"gram.noun.change.other": "changes",
"gram.noun.check.one": "check",
"gram.noun.check.other": "checks",
"gram.noun.child.one": "child",
"gram.noun.child.other": "children",
"gram.noun.commit.gender": "",
"gram.noun.commit.one": "commit",
"gram.noun.commit.other": "commits",
"gram.noun.dependency.one": "dependency",
"gram.noun.dependency.other": "dependencies",
"gram.noun.directory.one": "directory",
"gram.noun.directory.other": "directories",
"gram.noun.failed.one": "failed",
"gram.noun.failed.other": "failed",
"gram.noun.file.gender": "",
"gram.noun.file.one": "file",
"gram.noun.file.other": "files",
"gram.noun.issue.one": "issue",
"gram.noun.issue.other": "issues",
"gram.noun.item.gender": "",
"gram.noun.item.one": "item",
"gram.noun.item.other": "items",
"gram.noun.package.one": "package",
"gram.noun.package.other": "packages",
"gram.noun.passed.one": "passed",
"gram.noun.passed.other": "passed",
"gram.noun.person.one": "person",
"gram.noun.person.other": "people",
"gram.noun.query.one": "query",
"gram.noun.query.other": "queries",
"gram.noun.repo.gender": "",
"gram.noun.repo.one": "repo",
"gram.noun.repo.other": "repos",
"gram.noun.repository.one": "repository",
"gram.noun.repository.other": "repositories",
"gram.noun.skipped.one": "skipped",
"gram.noun.skipped.other": "skipped",
"gram.noun.task.one": "task",
"gram.noun.task.other": "tasks",
"gram.noun.test.one": "test",
"gram.noun.test.other": "tests",
"gram.noun.vulnerability.one": "vulnerability",
"gram.noun.vulnerability.other": "vulnerabilities",
"gram.number.decimal": ".",
"gram.number.percent": "%s%%",
"gram.number.thousands": ",",
"gram.punct.label": ":",
"gram.punct.progress": "...",
"gram.verb.analyse.base": "",
"gram.verb.analyse.gerund": "",
"gram.verb.analyse.past": "",
"gram.verb.be.base": "be",
"gram.verb.be.gerund": "being",
"gram.verb.be.past": "was",
"gram.verb.begin.base": "begin",
"gram.verb.begin.gerund": "beginning",
"gram.verb.begin.past": "began",
"gram.verb.bring.base": "bring",
"gram.verb.bring.gerund": "bringing",
"gram.verb.bring.past": "brought",
"gram.verb.build.base": "build",
"gram.verb.build.gerund": "building",
"gram.verb.build.past": "built",
"gram.verb.buy.base": "buy",
"gram.verb.buy.gerund": "buying",
"gram.verb.buy.past": "bought",
"gram.verb.catch.base": "catch",
"gram.verb.catch.gerund": "catching",
"gram.verb.catch.past": "caught",
"gram.verb.check.base": "",
"gram.verb.check.gerund": "",
"gram.verb.check.past": "",
"gram.verb.choose.base": "choose",
"gram.verb.choose.gerund": "choosing",
"gram.verb.choose.past": "chose",
"gram.verb.commit.base": "commit",
"gram.verb.commit.gerund": "committing",
"gram.verb.commit.past": "committed",
"gram.verb.create.base": "",
"gram.verb.create.gerund": "",
"gram.verb.create.past": "",
"gram.verb.cut.base": "cut",
"gram.verb.cut.gerund": "cutting",
"gram.verb.cut.past": "cut",
"gram.verb.delete.base": "",
"gram.verb.delete.gerund": "",
"gram.verb.delete.past": "",
"gram.verb.do.base": "do",
"gram.verb.do.gerund": "doing",
"gram.verb.do.past": "did",
"gram.verb.find.base": "find",
"gram.verb.find.gerund": "finding",
"gram.verb.find.past": "found",
"gram.verb.format.base": "format",
"gram.verb.format.gerund": "formatting",
"gram.verb.format.past": "formatted",
"gram.verb.get.base": "get",
"gram.verb.get.gerund": "getting",
"gram.verb.get.past": "got",
"gram.verb.go.base": "go",
"gram.verb.go.gerund": "going",
"gram.verb.go.past": "went",
"gram.verb.have.base": "have",
"gram.verb.have.gerund": "having",
"gram.verb.have.past": "had",
"gram.verb.hit.base": "hit",
"gram.verb.hit.gerund": "hitting",
"gram.verb.hit.past": "hit",
"gram.verb.hold.base": "hold",
"gram.verb.hold.gerund": "holding",
"gram.verb.hold.past": "held",
"gram.verb.install.base": "",
"gram.verb.install.gerund": "",
"gram.verb.install.past": "",
"gram.verb.keep.base": "keep",
"gram.verb.keep.gerund": "keeping",
"gram.verb.keep.past": "kept",
"gram.verb.lead.base": "lead",
"gram.verb.lead.gerund": "leading",
"gram.verb.lead.past": "led",
"gram.verb.leave.base": "leave",
"gram.verb.leave.gerund": "leaving",
"gram.verb.leave.past": "left",
"gram.verb.lose.base": "lose",
"gram.verb.lose.gerund": "losing",
"gram.verb.lose.past": "lost",
"gram.verb.make.base": "make",
"gram.verb.make.gerund": "making",
"gram.verb.make.past": "made",
"gram.verb.meet.base": "meet",
"gram.verb.meet.gerund": "meeting",
"gram.verb.meet.past": "met",
"gram.verb.organise.base": "",
"gram.verb.organise.gerund": "",
"gram.verb.organise.past": "",
"gram.verb.pay.base": "pay",
"gram.verb.pay.gerund": "paying",
"gram.verb.pay.past": "paid",
"gram.verb.pull.base": "",
"gram.verb.pull.gerund": "",
"gram.verb.pull.past": "",
"gram.verb.push.base": "",
"gram.verb.push.gerund": "",
"gram.verb.push.past": "",
"gram.verb.put.base": "put",
"gram.verb.put.gerund": "putting",
"gram.verb.put.past": "put",
"gram.verb.realise.base": "",
"gram.verb.realise.gerund": "",
"gram.verb.realise.past": "",
"gram.verb.recognise.base": "",
"gram.verb.recognise.gerund": "",
"gram.verb.recognise.past": "",
"gram.verb.run.base": "run",
"gram.verb.run.gerund": "running",
"gram.verb.run.past": "ran",
"gram.verb.save.base": "",
"gram.verb.save.gerund": "",
"gram.verb.save.past": "",
"gram.verb.scan.base": "scan",
"gram.verb.scan.gerund": "scanning",
"gram.verb.scan.past": "scanned",
"gram.verb.sell.base": "sell",
"gram.verb.sell.gerund": "selling",
"gram.verb.sell.past": "sold",
"gram.verb.send.base": "send",
"gram.verb.send.gerund": "sending",
"gram.verb.send.past": "sent",
"gram.verb.set.base": "set",
"gram.verb.set.gerund": "setting",
"gram.verb.set.past": "set",
"gram.verb.shut.base": "shut",
"gram.verb.shut.gerund": "shutting",
"gram.verb.shut.past": "shut",
"gram.verb.sit.base": "sit",
"gram.verb.sit.gerund": "sitting",
"gram.verb.sit.past": "sat",
"gram.verb.spend.base": "spend",
"gram.verb.spend.gerund": "spending",
"gram.verb.spend.past": "spent",
"gram.verb.split.base": "split",
"gram.verb.split.gerund": "splitting",
"gram.verb.split.past": "split",
"gram.verb.stop.base": "stop",
"gram.verb.stop.gerund": "stopping",
"gram.verb.stop.past": "stopped",
"gram.verb.take.base": "take",
"gram.verb.take.gerund": "taking",
"gram.verb.take.past": "took",
"gram.verb.think.base": "think",
"gram.verb.think.gerund": "thinking",
"gram.verb.think.past": "thought",
"gram.verb.update.base": "",
"gram.verb.update.gerund": "",
"gram.verb.update.past": "",
"gram.verb.win.base": "win",
"gram.verb.win.gerund": "winning",
"gram.verb.win.past": "won",
"gram.verb.write.base": "write",
"gram.verb.write.gerund": "writing",
"gram.verb.write.past": "wrote",
"gram.word.api": "API",
"gram.word.app_url": "app URL",
"gram.word.blocked_by": "blocked by",
"gram.word.cgo": "CGO",
"gram.word.ci": "CI",
"gram.word.claimed_by": "claimed by",
"gram.word.coverage": "coverage",
"gram.word.cpus": "CPUs",
"gram.word.dry_run": "dry run",
"gram.word.failed": "failed",
"gram.word.filter": "filter",
"gram.word.go_mod": "go.mod",
"gram.word.html": "HTML",
"gram.word.id": "ID",
"gram.word.ok": "OK",
"gram.word.package": "package",
"gram.word.passed": "passed",
"gram.word.php": "PHP",
"gram.word.pid": "PID",
"gram.word.pnpm": "pnpm",
"gram.word.pr": "PR",
"gram.word.qa": "QA",
"gram.word.related_files": "related files",
"gram.word.sdk": "SDK",
"gram.word.skipped": "skipped",
"gram.word.ssh": "SSH",
"gram.word.ssl": "SSL",
"gram.word.test": "test",
"gram.word.up_to_date": "up to date",
"gram.word.url": "URL",
"gram.word.vite": "Vite",
"lang.de": "German",
"lang.en": "English",
"lang.es": "Spanish",
"lang.fr": "French",
"lang.zh": "Chinese",
"prompt.confirm": "Are you sure?",
"prompt.continue": "Continue?",
"prompt.discard": "Discard changes?",
"prompt.no": "n",
"prompt.overwrite": "Overwrite?",
"prompt.proceed": "Proceed?",
"prompt.yes": "y",
"time.ago.day.one": "{{.Count}} day ago",
"time.ago.day.other": "{{.Count}} days ago",
"time.ago.hour.one": "{{.Count}} hour ago",
"time.ago.hour.other": "{{.Count}} hours ago",
"time.ago.minute.one": "{{.Count}} minute ago",
"time.ago.minute.other": "{{.Count}} minutes ago",
"time.ago.second.one": "{{.Count}} second ago",
"time.ago.second.other": "{{.Count}} seconds ago",
"time.ago.week.one": "{{.Count}} week ago",
"time.ago.week.other": "{{.Count}} weeks ago",
"time.just_now": "just now"
"gram": {
"verb": {
"be": { "base": "be", "past": "was", "gerund": "being" },
"go": { "base": "go", "past": "went", "gerund": "going" },
"do": { "base": "do", "past": "did", "gerund": "doing" },
"have": { "base": "have", "past": "had", "gerund": "having" },
"make": { "base": "make", "past": "made", "gerund": "making" },
"get": { "base": "get", "past": "got", "gerund": "getting" },
"run": { "base": "run", "past": "ran", "gerund": "running" },
"write": { "base": "write", "past": "wrote", "gerund": "writing" },
"build": { "base": "build", "past": "built", "gerund": "building" },
"send": { "base": "send", "past": "sent", "gerund": "sending" },
"find": { "base": "find", "past": "found", "gerund": "finding" },
"take": { "base": "take", "past": "took", "gerund": "taking" },
"begin": { "base": "begin", "past": "began", "gerund": "beginning" },
"keep": { "base": "keep", "past": "kept", "gerund": "keeping" },
"hold": { "base": "hold", "past": "held", "gerund": "holding" },
"bring": { "base": "bring", "past": "brought", "gerund": "bringing" },
"think": { "base": "think", "past": "thought", "gerund": "thinking" },
"buy": { "base": "buy", "past": "bought", "gerund": "buying" },
"catch": { "base": "catch", "past": "caught", "gerund": "catching" },
"choose": { "base": "choose", "past": "chose", "gerund": "choosing" },
"lose": { "base": "lose", "past": "lost", "gerund": "losing" },
"win": { "base": "win", "past": "won", "gerund": "winning" },
"meet": { "base": "meet", "past": "met", "gerund": "meeting" },
"lead": { "base": "lead", "past": "led", "gerund": "leading" },
"leave": { "base": "leave", "past": "left", "gerund": "leaving" },
"spend": { "base": "spend", "past": "spent", "gerund": "spending" },
"pay": { "base": "pay", "past": "paid", "gerund": "paying" },
"sell": { "base": "sell", "past": "sold", "gerund": "selling" },
"commit": { "base": "commit", "past": "committed", "gerund": "committing" },
"stop": { "base": "stop", "past": "stopped", "gerund": "stopping" },
"scan": { "base": "scan", "past": "scanned", "gerund": "scanning" },
"format": { "base": "format", "past": "formatted", "gerund": "formatting" },
"set": { "base": "set", "past": "set", "gerund": "setting" },
"put": { "base": "put", "past": "put", "gerund": "putting" },
"cut": { "base": "cut", "past": "cut", "gerund": "cutting" },
"hit": { "base": "hit", "past": "hit", "gerund": "hitting" },
"sit": { "base": "sit", "past": "sat", "gerund": "sitting" },
"split": { "base": "split", "past": "split", "gerund": "splitting" },
"shut": { "base": "shut", "past": "shut", "gerund": "shutting" },
"check": { "base": "check", "past": "checked", "gerund": "checking" },
"create": { "base": "create", "past": "created", "gerund": "creating" },
"delete": { "base": "delete", "past": "deleted", "gerund": "deleting" },
"install": { "base": "install", "past": "installed", "gerund": "installing" },
"update": { "base": "update", "past": "updated", "gerund": "updating" },
"pull": { "base": "pull", "past": "pulled", "gerund": "pulling" },
"push": { "base": "push", "past": "pushed", "gerund": "pushing" },
"save": { "base": "save", "past": "saved", "gerund": "saving" },
"analyse": { "base": "analyse", "past": "analysed", "gerund": "analysing" },
"organise": { "base": "organise", "past": "organised", "gerund": "organising" },
"realise": { "base": "realise", "past": "realised", "gerund": "realising" },
"recognise": { "base": "recognise", "past": "recognised", "gerund": "recognising" }
},
"noun": {
"file": { "one": "file", "other": "files" },
"repo": { "one": "repo", "other": "repos" },
"repository": { "one": "repository", "other": "repositories" },
"commit": { "one": "commit", "other": "commits" },
"branch": { "one": "branch", "other": "branches" },
"change": { "one": "change", "other": "changes" },
"item": { "one": "item", "other": "items" },
"issue": { "one": "issue", "other": "issues" },
"task": { "one": "task", "other": "tasks" },
"person": { "one": "person", "other": "people" },
"child": { "one": "child", "other": "children" },
"package": { "one": "package", "other": "packages" },
"artifact": { "one": "artifact", "other": "artifacts" },
"vulnerability": { "one": "vulnerability", "other": "vulnerabilities" },
"dependency": { "one": "dependency", "other": "dependencies" },
"directory": { "one": "directory", "other": "directories" },
"category": { "one": "category", "other": "categories" },
"query": { "one": "query", "other": "queries" },
"check": { "one": "check", "other": "checks" },
"test": { "one": "test", "other": "tests" }
},
"article": {
"indefinite": { "default": "a", "vowel": "an" },
"definite": "the"
},
"word": {
"url": "URL", "id": "ID", "ok": "OK", "ci": "CI", "qa": "QA",
"php": "PHP", "sdk": "SDK", "html": "HTML", "cgo": "CGO", "pid": "PID",
"cpus": "CPUs", "ssh": "SSH", "ssl": "SSL", "api": "API", "pr": "PR",
"vite": "Vite", "pnpm": "pnpm",
"app_url": "app URL", "blocked_by": "blocked by", "claimed_by": "claimed by",
"related_files": "related files", "up_to_date": "up to date",
"dry_run": "dry run", "go_mod": "go.mod",
"coverage": "coverage", "failed": "failed", "filter": "filter",
"package": "package", "passed": "passed", "skipped": "skipped", "test": "test"
},
"punct": {
"label": ":",
"progress": "..."
},
"number": {
"thousands": ",",
"decimal": ".",
"percent": "%s%%"
}
},
"lang": {
"de": "German", "en": "English", "es": "Spanish",
"fr": "French", "ru": "Russian", "zh": "Chinese"
},
"prompt": {
"yes": "y", "no": "n",
"continue": "Continue?", "proceed": "Proceed?",
"confirm": "Are you sure?", "overwrite": "Overwrite?",
"discard": "Discard changes?"
},
"time": {
"just_now": "just now",
"ago": {
"second": { "one": "{{.Count}} second ago", "other": "{{.Count}} seconds ago" },
"minute": { "one": "{{.Count}} minute ago", "other": "{{.Count}} minutes ago" },
"hour": { "one": "{{.Count}} hour ago", "other": "{{.Count}} hours ago" },
"day": { "one": "{{.Count}} day ago", "other": "{{.Count}} days ago" },
"week": { "one": "{{.Count}} week ago", "other": "{{.Count}} weeks ago" }
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1 +1,148 @@
{}
{
"gram": {
"verb": {
"be": { "base": "是", "past": "是", "gerund": "状态" },
"go": { "base": "前往", "past": "前往", "gerund": "前往" },
"do": { "base": "执行", "past": "执行", "gerund": "执行" },
"have": { "base": "拥有", "past": "拥有", "gerund": "拥有" },
"make": { "base": "创建", "past": "创建", "gerund": "创建" },
"get": { "base": "获取", "past": "获取", "gerund": "获取" },
"run": { "base": "运行", "past": "运行", "gerund": "运行" },
"write": { "base": "写入", "past": "写入", "gerund": "写入" },
"build": { "base": "构建", "past": "构建", "gerund": "构建" },
"send": { "base": "发送", "past": "发送", "gerund": "发送" },
"find": { "base": "查找", "past": "查找", "gerund": "查找" },
"take": { "base": "获取", "past": "获取", "gerund": "获取" },
"begin": { "base": "开始", "past": "开始", "gerund": "开始" },
"keep": { "base": "保持", "past": "保持", "gerund": "保持" },
"hold": { "base": "持有", "past": "持有", "gerund": "持有" },
"bring": { "base": "带来", "past": "带来", "gerund": "带来" },
"think": { "base": "思考", "past": "思考", "gerund": "思考" },
"choose": { "base": "选择", "past": "选择", "gerund": "选择" },
"lose": { "base": "丢失", "past": "丢失", "gerund": "丢失" },
"win": { "base": "成功", "past": "成功", "gerund": "成功" },
"meet": { "base": "匹配", "past": "匹配", "gerund": "匹配" },
"lead": { "base": "引导", "past": "引导", "gerund": "引导" },
"leave": { "base": "离开", "past": "离开", "gerund": "离开" },
"commit": { "base": "提交", "past": "提交", "gerund": "提交" },
"stop": { "base": "停止", "past": "停止", "gerund": "停止" },
"scan": { "base": "扫描", "past": "扫描", "gerund": "扫描" },
"format": { "base": "格式化", "past": "格式化", "gerund": "格式化" },
"set": { "base": "设置", "past": "设置", "gerund": "设置" },
"check": { "base": "检查", "past": "检查", "gerund": "检查" },
"create": { "base": "创建", "past": "创建", "gerund": "创建" },
"delete": { "base": "删除", "past": "删除", "gerund": "删除" },
"install": { "base": "安装", "past": "安装", "gerund": "安装" },
"update": { "base": "更新", "past": "更新", "gerund": "更新" },
"pull": { "base": "拉取", "past": "拉取", "gerund": "拉取" },
"push": { "base": "推送", "past": "推送", "gerund": "推送" },
"save": { "base": "保存", "past": "保存", "gerund": "保存" },
"analyse": { "base": "分析", "past": "分析", "gerund": "分析" },
"organise": { "base": "整理", "past": "整理", "gerund": "整理" },
"test": { "base": "测试", "past": "测试", "gerund": "测试" },
"deploy": { "base": "部署", "past": "部署", "gerund": "部署" },
"clone": { "base": "克隆", "past": "克隆", "gerund": "克隆" },
"compile": { "base": "编译", "past": "编译", "gerund": "编译" },
"download": { "base": "下载", "past": "下载", "gerund": "下载" },
"upload": { "base": "上传", "past": "上传", "gerund": "上传" }
},
"noun": {
"file": { "one": "文件", "other": "文件" },
"repo": { "one": "仓库", "other": "仓库" },
"repository": { "one": "仓库", "other": "仓库" },
"commit": { "one": "提交", "other": "提交" },
"branch": { "one": "分支", "other": "分支" },
"change": { "one": "更改", "other": "更改" },
"item": { "one": "项", "other": "项" },
"issue": { "one": "问题", "other": "问题" },
"task": { "one": "任务", "other": "任务" },
"person": { "one": "人", "other": "人" },
"child": { "one": "子项", "other": "子项" },
"package": { "one": "包", "other": "包" },
"artifact": { "one": "构件", "other": "构件" },
"vulnerability": { "one": "漏洞", "other": "漏洞" },
"dependency": { "one": "依赖", "other": "依赖" },
"directory": { "one": "目录", "other": "目录" },
"category": { "one": "分类", "other": "分类" },
"query": { "one": "查询", "other": "查询" },
"check": { "one": "检查", "other": "检查" },
"test": { "one": "测试", "other": "测试" },
"error": { "one": "错误", "other": "错误" },
"warning": { "one": "警告", "other": "警告" },
"service": { "one": "服务", "other": "服务" },
"config": { "one": "配置", "other": "配置" },
"workflow": { "one": "工作流", "other": "工作流" }
},
"article": {
"indefinite": { "default": "", "vowel": "" },
"definite": ""
},
"word": {
"url": "URL", "id": "ID", "ok": "OK", "ci": "CI", "qa": "QA",
"php": "PHP", "sdk": "SDK", "html": "HTML", "cgo": "CGO", "pid": "PID",
"cpus": "CPU", "ssh": "SSH", "ssl": "SSL", "api": "API", "pr": "PR",
"vite": "Vite", "pnpm": "pnpm",
"app_url": "应用 URL", "blocked_by": "被阻塞",
"claimed_by": "已认领", "related_files": "相关文件",
"up_to_date": "已是最新", "dry_run": "模拟运行",
"go_mod": "go.mod", "coverage": "覆盖率", "failed": "失败",
"filter": "过滤器", "package": "包", "passed": "通过",
"skipped": "跳过", "test": "测试"
},
"punct": {
"label": "",
"progress": "..."
},
"number": {
"thousands": ",",
"decimal": ".",
"percent": "%s%%"
}
},
"cli.aborted": "已中止。",
"cli.fail": "失败",
"cli.pass": "通过",
"lang": {
"de": "德语", "en": "英语", "es": "西班牙语",
"fr": "法语", "ru": "俄语", "zh": "中文"
},
"prompt": {
"yes": "是", "no": "否",
"continue": "继续?", "proceed": "执行?",
"confirm": "确定吗?", "overwrite": "覆盖?",
"discard": "放弃更改?"
},
"time": {
"just_now": "刚刚",
"ago": {
"second": { "other": "{{.Count}} 秒前" },
"minute": { "other": "{{.Count}} 分钟前" },
"hour": { "other": "{{.Count}} 小时前" },
"day": { "other": "{{.Count}} 天前" },
"week": { "other": "{{.Count}} 周前" }
}
},
"error.gh_not_found": "未找到 'gh' CLI 工具。请安装https://cli.github.com/",
"error.registry_not_found": "未找到 repos.yaml",
"error.repo_not_found": "未找到仓库 '{{.Name}}'",
"common.label.done": "完成",
"common.label.error": "错误",
"common.label.info": "信息",
"common.label.success": "成功",
"common.label.warning": "警告",
"common.status.clean": "干净",
"common.status.dirty": "已修改",
"common.status.running": "运行中",
"common.status.stopped": "已停止",
"common.status.up_to_date": "已是最新",
"common.result.all_passed": "所有测试通过",
"common.result.no_issues": "未发现问题",
"common.prompt.abort": "已中止。",
"common.success.completed": "{{.Action}} 成功完成"
}