Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ brief crate:serde
brief pypi:requests
```

Local scans recurse through the project tree. Use `--scan-depth N` to limit recursion, `--skip dir1,dir2` to add directory exclusions, or `--tracked` to consider only files tracked by Git. When Rust source exists without a root `Cargo.toml`, brief uses the shallowest `Cargo.toml` within the scan depth as an additional manifest root. Cargo workspace member manifests are also checked for tool configuration.
Local scans inspect up to eight directory levels and 10,000 filesystem entries by default. Use `--scan-depth N` or `--scan-limit N` to change those bounds. External line counters have a two-second limit, configurable with `--line-count-timeout D`, and reports mark truncated scans. Set any of these three values to `0` to remove that bound. Use `--skip dir1,dir2` to add directory exclusions, or `--tracked` to consider only files tracked by Git. When Rust source exists without a root `Cargo.toml`, brief uses the shallowest `Cargo.toml` within the scan depth as an additional manifest root. Cargo workspace member manifests are also checked for tool configuration.

Remote sources are shallow-cloned by default. Use `--depth 0` for a full clone, `--keep` to preserve the clone, or `--dir ./somewhere` to clone into a specific directory. Use `--cache ./cache` to keep one shallow checkout per HTTPS URL and reuse it across runs. Cache mode cannot be combined with `--depth 0` or `--dir`.

Expand Down
12 changes: 7 additions & 5 deletions brief.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,13 @@ type LineCount struct {

// Stats holds performance and coverage metrics from the detection run.
type Stats struct {
Duration time.Duration `json:"-"`
DurationMS float64 `json:"duration_ms"`
FilesChecked int `json:"files_checked"`
ToolsMatched int `json:"tools_matched"`
ToolsChecked int `json:"tools_checked"`
Duration time.Duration `json:"-"`
DurationMS float64 `json:"duration_ms"`
FilesChecked int `json:"files_checked"`
ToolsMatched int `json:"tools_matched"`
ToolsChecked int `json:"tools_checked"`
ScanEntries int `json:"scan_entries"`
ScanTruncated bool `json:"scan_truncated,omitempty"`
}

// DepInfo is a parsed dependency from a manifest file.
Expand Down
6 changes: 6 additions & 0 deletions cmd/brief/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func cmdDiff(args []string) {
markdownFlag := fs.Bool("markdown", false, "Force markdown output")
verbose := fs.Bool("verbose", false, "Include breadcrumb/reference information")
category := fs.String("category", "", "Only report on specific category")
scanDepth := fs.Int("scan-depth", detect.DefaultScanDepth, "Max directory depth for recursive detection (0 = unlimited)")
scanLimit := fs.Int("scan-limit", detect.DefaultScanLimit, "Max filesystem entries to scan (0 = unlimited)")
lineCountTimeout := fs.Duration("line-count-timeout", detect.DefaultLineCountTimeout, "Max time for line counting (0 = unlimited)")
_ = fs.Parse(args)

// Determine the project root (git toplevel).
Expand Down Expand Up @@ -84,6 +87,9 @@ func cmdDiff(args []string) {
}()

engine := detect.New(knowledgeBase, root)
engine.ScanDepth = *scanDepth
engine.ScanLimit = *scanLimit
engine.LineCountTimeout = *lineCountTimeout
r, err := engine.Run()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
Expand Down
20 changes: 17 additions & 3 deletions cmd/brief/enrich.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"path/filepath"
"strings"
"sync"
"time"

"github.com/BurntSushi/toml"
"github.com/git-pkgs/brief"
Expand All @@ -31,7 +32,9 @@ func cmdEnrich(args []string) {
depth := fs.Int("depth", -1, "Git clone depth (0 = full clone, default shallow)")
dir := fs.String("dir", "", "Directory to clone remote source into")
cache := fs.String("cache", "", "Persistent shallow cache for HTTPS remotes (incompatible with -depth 0 and -dir)")
scanDepth := fs.Int("scan-depth", 0, "Max directory depth for language detection (0 = unlimited)")
scanDepth := fs.Int("scan-depth", detect.DefaultScanDepth, "Max directory depth for recursive detection (0 = unlimited)")
scanLimit := fs.Int("scan-limit", detect.DefaultScanLimit, "Max filesystem entries to scan (0 = unlimited)")
lineCountTimeout := fs.Duration("line-count-timeout", detect.DefaultLineCountTimeout, "Max time for line counting (0 = unlimited)")
skip := fs.String("skip", "", "Additional directories to skip, comma-separated")
_ = fs.Parse(args)

Expand All @@ -51,12 +54,21 @@ func cmdEnrich(args []string) {
os.Exit(1)
}

code := runEnrich(src.Dir, *scanDepth, *skip, *jsonFlag, *humanFlag, *markdownFlag, *verbose)
code := runEnrich(
src.Dir, *scanDepth, *scanLimit, *lineCountTimeout, *skip,
*jsonFlag, *humanFlag, *markdownFlag, *verbose,
)
src.Cleanup()
os.Exit(code)
}

func runEnrich(dir string, scanDepth int, skip string, jsonFlag, humanFlag, markdownFlag, verbose bool) int {
func runEnrich(
dir string,
scanDepth, scanLimit int,
lineCountTimeout time.Duration,
skip string,
jsonFlag, humanFlag, markdownFlag, verbose bool,
) int {
knowledgeBase, err := kb.Load(brief.KnowledgeFS)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error loading knowledge base: %v\n", err)
Expand All @@ -65,6 +77,8 @@ func runEnrich(dir string, scanDepth int, skip string, jsonFlag, humanFlag, mark

engine := detect.New(knowledgeBase, dir)
engine.ScanDepth = scanDepth
engine.ScanLimit = scanLimit
engine.LineCountTimeout = lineCountTimeout
if skip != "" {
engine.SkipDirs = strings.Split(skip, ",")
}
Expand Down
19 changes: 16 additions & 3 deletions cmd/brief/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ func cmdScan(args []string) {
depth := fs.Int("depth", -1, "Git clone depth (0 = full clone, default shallow)")
dir := fs.String("dir", "", "Directory to clone remote source into")
cache := fs.String("cache", "", "Persistent shallow cache for HTTPS remotes (incompatible with -depth 0 and -dir)")
scanDepth := fs.Int("scan-depth", 0, "Max directory depth for language detection (0 = unlimited)")
scanDepth := fs.Int("scan-depth", detect.DefaultScanDepth, "Max directory depth for recursive detection (0 = unlimited)")
scanLimit := fs.Int("scan-limit", detect.DefaultScanLimit, "Max filesystem entries to scan (0 = unlimited)")
lineCountTimeout := fs.Duration("line-count-timeout", detect.DefaultLineCountTimeout, "Max time for line counting (0 = unlimited)")
skip := fs.String("skip", "", "Additional directories to skip, comma-separated")
tracked := fs.Bool("tracked", false, "Only consider files tracked by git")
version := fs.Bool("version", false, "Print version and exit")
Expand Down Expand Up @@ -115,12 +117,21 @@ func cmdScan(args []string) {
os.Exit(1)
}

code := runScan(src.Dir, *scanDepth, *skip, *category, *tracked, *jsonFlag, *humanFlag, *markdownFlag, *verbose)
code := runScan(
src.Dir, *scanDepth, *scanLimit, *lineCountTimeout, *skip, *category,
*tracked, *jsonFlag, *humanFlag, *markdownFlag, *verbose,
)
src.Cleanup()
os.Exit(code)
}

func runScan(dir string, scanDepth int, skip, category string, tracked, jsonFlag, humanFlag, markdownFlag, verbose bool) int {
func runScan(
dir string,
scanDepth, scanLimit int,
lineCountTimeout time.Duration,
skip, category string,
tracked, jsonFlag, humanFlag, markdownFlag, verbose bool,
) int {
knowledgeBase, err := kb.Load(brief.KnowledgeFS)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error loading knowledge base: %v\n", err)
Expand All @@ -129,6 +140,8 @@ func runScan(dir string, scanDepth int, skip, category string, tracked, jsonFlag

engine := detect.New(knowledgeBase, dir)
engine.ScanDepth = scanDepth
engine.ScanLimit = scanLimit
engine.LineCountTimeout = lineCountTimeout
engine.TrackedOnly = tracked
if skip != "" {
engine.SkipDirs = strings.Split(skip, ",")
Expand Down
106 changes: 106 additions & 0 deletions cmd/brief/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package main

import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/git-pkgs/brief"
)

const scanHelperRootEnv = "BRIEF_SCAN_HELPER_ROOT"
const diffHelperEnv = "BRIEF_DIFF_HELPER"

func TestScanDefaultsBoundRecursiveDetection(t *testing.T) {
if root := os.Getenv(scanHelperRootEnv); root != "" {
cmdScan([]string{"-json", root})
return
}

root := t.TempDir()
writeScanFixture(t, root, "pyproject.toml", "[project]\nname = \"example\"\nversion = \"1.0.0\"\n")
writeScanFixture(t, root, "pipelines/example/Snakefile", "rule all:\n")
deep := filepath.Join(
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "asv.conf.json",
)
writeScanFixture(t, root, deep, "{}\n")

cmd := exec.Command(os.Args[0], "-test.run=^TestScanDefaultsBoundRecursiveDetection$")
cmd.Env = append(os.Environ(), scanHelperRootEnv+"="+root, "PATH=")
out, err := cmd.Output()
if err != nil {
t.Fatalf("scan command failed: %v", err)
}

var report brief.Report
if err := json.Unmarshal(out, &report); err != nil {
t.Fatalf("parsing scan output: %v\n%s", err, out)
}
if !reportHasTool(&report, "build", "Snakemake") {
t.Fatal("expected nested Snakemake project within the default depth")
}
if reportHasTool(&report, "test", "ASV") {
t.Fatal("did not expect ASV beyond the default depth")
}
if !report.Stats.ScanTruncated {
t.Fatal("expected scan output to report the depth truncation")
}
}

func TestDiffAppliesScanOverrides(t *testing.T) {
if os.Getenv(diffHelperEnv) != "" {
cmdDiff([]string{"-scan-limit=-1", "HEAD"})
return
}

dir := t.TempDir()
writeScanFixture(t, dir, "go.mod", "module example.com/project\n\ngo 1.22\n")
writeScanFixture(t, dir, "main.go", "package main\n")
runGitFixture(t, dir, "init", "-q")
runGitFixture(t, dir, "add", "go.mod", "main.go")
runGitFixture(t, dir, "-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-q", "-m", "initial")
writeScanFixture(t, dir, "main.go", "package main\n\nfunc main() {}\n")

cmd := exec.Command(os.Args[0], "-test.run=^TestDiffAppliesScanOverrides$")
cmd.Dir = dir
cmd.Env = append(os.Environ(), diffHelperEnv+"=1", "PATH="+os.Getenv("PATH"))
out, err := cmd.CombinedOutput()
if err == nil {
t.Fatalf("diff command succeeded with a negative scan limit\n%s", out)
}
if !strings.Contains(string(out), "scan limit must not be negative") {
t.Fatalf("diff command output = %q, want scan limit validation error", out)
}
}

func runGitFixture(t *testing.T, dir string, args ...string) {
t.Helper()
cmd := exec.Command("git", args...)
cmd.Dir = dir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v: %v\n%s", args, err, out)
}
}

func writeScanFixture(t *testing.T, root, name, content string) {
t.Helper()
full := filepath.Join(root, name)
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}

func reportHasTool(report *brief.Report, category, name string) bool {
for _, tool := range report.Tools[category] {
if tool.Name == name {
return true
}
}
return false
}
6 changes: 5 additions & 1 deletion cmd/brief/threat.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ func runDetection(name string, args []string) (*detect.Engine, *brief.Report, ou
jsonFlag := fs.Bool("json", false, "Force JSON output")
humanFlag := fs.Bool("human", false, "Force human-readable output")
markdownFlag := fs.Bool("markdown", false, "Force markdown output")
scanDepth := fs.Int("scan-depth", 0, "Max directory depth for language detection (0 = unlimited)")
scanDepth := fs.Int("scan-depth", detect.DefaultScanDepth, "Max directory depth for recursive detection (0 = unlimited)")
scanLimit := fs.Int("scan-limit", detect.DefaultScanLimit, "Max filesystem entries to scan (0 = unlimited)")
lineCountTimeout := fs.Duration("line-count-timeout", detect.DefaultLineCountTimeout, "Max time for line counting (0 = unlimited)")
skip := fs.String("skip", "", "Additional directories to skip, comma-separated")
_ = fs.Parse(args)

Expand All @@ -47,6 +49,8 @@ func runDetection(name string, args []string) (*detect.Engine, *brief.Report, ou

engine := detect.New(knowledgeBase, path)
engine.ScanDepth = *scanDepth
engine.ScanLimit = *scanLimit
engine.LineCountTimeout = *lineCountTimeout
if *skip != "" {
engine.SkipDirs = strings.Split(*skip, ",")
}
Expand Down
Loading