Skip to content

Commit 5495b18

Browse files
committed
feat: add --exclude-dir/--exclude-glob CLI flags, skip examples/docs by default, --ci implies summary, add github output format
1 parent a619630 commit 5495b18

4 files changed

Lines changed: 77 additions & 2 deletions

File tree

src/mcts/cli/main.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ def scan(
493493
str | None,
494494
typer.Option(
495495
"--terminal-format",
496-
help="Terminal layout: table, by_tool, by_analyzer, by_severity, summary",
496+
help="Terminal layout: table, by_tool, by_analyzer, by_severity, summary, github",
497497
),
498498
] = None,
499499
tool_filter: Annotated[
@@ -553,9 +553,23 @@ def scan(
553553
bool,
554554
typer.Option(
555555
"--ci",
556-
help="Apply CI gate preset (fail-on-critical, min-score 70) and print score breakdown on failure",
556+
help="Apply CI gate preset (fail-on-critical, min-score 70, summary output) and print score breakdown on failure",
557557
),
558558
] = False,
559+
exclude_glob: Annotated[
560+
str | None,
561+
typer.Option(
562+
"--exclude-glob",
563+
help="Comma-separated glob patterns for files/dirs to exclude from scan",
564+
),
565+
] = None,
566+
exclude_dir: Annotated[
567+
str | None,
568+
typer.Option(
569+
"--exclude-dir",
570+
help="Comma-separated directory names to exclude from scan (e.g. examples,docs)",
571+
),
572+
] = None,
559573
policy: Annotated[
560574
Path | None,
561575
typer.Option("--policy", help="Governance policy YAML (default: .mcts/policy.yaml)"),
@@ -660,6 +674,7 @@ def scan(
660674

661675
from mcts.cli.auto import AutoScanError, resolve_auto_scan
662676
from mcts.cli.machine_wide import run_machine_wide_cli
677+
from mcts.core.config import DEFAULT_EXCLUDE_DIRS
663678
from mcts.governance import evaluate_policy, load_policy
664679
from mcts.probe.consent import LiveProbeConsentError, live_consent_granted
665680
from mcts.probe.session import MCPProbeError
@@ -727,6 +742,8 @@ def scan(
727742
tool_filters = [p.strip() for p in tool_filter.split(",") if p.strip()] if tool_filter else []
728743
analyzer_filters = [p.strip() for p in analyzer_filter.split(",") if p.strip()] if analyzer_filter else []
729744
severity_filters = [p.strip() for p in severity_filter.split(",") if p.strip()] if severity_filter else []
745+
exclude_globs = [p.strip() for p in exclude_glob.split(",") if p.strip()] if exclude_glob else []
746+
extra_exclude_dirs = [p.strip() for p in exclude_dir.split(",") if p.strip()] if exclude_dir else []
730747
analyzer_list = [p.strip() for p in analyzers.split(",") if p.strip()] if analyzers else []
731748
technique_filters: list[str] = []
732749
if technique:
@@ -742,6 +759,9 @@ def scan(
742759
fail_on_critical = True
743760
if min_score is None:
744761
min_score = 70
762+
if terminal_format is None:
763+
terminal_format = "summary"
764+
hide_safe = True
745765

746766
try:
747767
resolved_theme = get_theme(theme)
@@ -859,6 +879,8 @@ def scan(
859879
weights_profile=weights_profile,
860880
corpus_stats_path=corpus_stats_path,
861881
assets_path=assets_path,
882+
exclude_globs=exclude_globs,
883+
exclude_dirs=list(DEFAULT_EXCLUDE_DIRS) + extra_exclude_dirs,
862884
)
863885

864886
try:

src/mcts/discovery/onboarding.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
".mypy_cache",
2828
".pytest_cache",
2929
".ruff_cache",
30+
"examples",
31+
"docs",
32+
"scripts",
3033
}
3134
)
3235

src/mcts/discovery/static.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
'Server("mcp")',
2626
)
2727

28+
<<<<<<< Updated upstream
2829
DEFAULT_SKIP_PATH_PARTS = frozenset(
2930
{
3031
"tests",
@@ -43,6 +44,9 @@
4344
"build",
4445
}
4546
)
47+
=======
48+
DEFAULT_SKIP_PATH_PARTS = frozenset({"tests", "test", "examples", "docs", "scripts"})
49+
>>>>>>> Stashed changes
4650

4751
TYPE_HINT_MAP: dict[str, str] = {
4852
"str": "string",

src/mcts/ui/alternate_formats.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def render_report(
3232
_render_grouped(findings, console, key=lambda f: f.severity.value)
3333
elif fmt == "summary":
3434
_render_summary(report, findings, console)
35+
elif fmt == "github":
36+
_render_github(report, findings, console)
3537
else:
3638
raise ValueError(f"Unknown terminal format: {fmt}")
3739

@@ -85,3 +87,47 @@ def _render_summary(report: ScanReport, findings: list[Finding], console: Consol
8587
console.print(line)
8688
for f in findings[:10]:
8789
console.print(f" [{f.severity.value}] {f.title}")
90+
91+
92+
def _render_github(report: ScanReport, findings: list[Finding], console: Console) -> None:
93+
"""Render a concise GitHub-issue-compatible summary in plain text."""
94+
from collections import Counter
95+
96+
sev_counts = Counter(f.severity.value for f in findings)
97+
score = report.score_v2 or report.score
98+
risk = getattr(score, "risk_level", None) or "unknown"
99+
100+
lines = [
101+
"## MCTS Security Scan",
102+
"",
103+
f"**Score:** {report.score.overall}/100 | **Risk:** {risk}",
104+
f"**Total findings:** {len(findings)}",
105+
"",
106+
"### Severity Breakdown",
107+
"| Severity | Count |",
108+
"|---|---|",
109+
]
110+
for sev in ("critical", "high", "medium", "low", "info"):
111+
if sev_counts.get(sev, 0):
112+
lines.append(f"| {sev.capitalize()} | {sev_counts[sev]} |")
113+
lines.append("")
114+
115+
if findings:
116+
lines.append("### Top Findings")
117+
lines.append("")
118+
for f in findings[:15]:
119+
tool = f" ({f.tool})" if f.tool else ""
120+
source = (f.location.file if f.location else "") or ""
121+
if source and len(source) > 80:
122+
source = "..." + source[-77:]
123+
src = f" — `{source}`" if source else ""
124+
lines.append(f"- **[{f.severity.value.upper()}]** {f.title}{tool}{src}")
125+
126+
lines.append("")
127+
lines.append("---")
128+
lines.append(
129+
"*Scan by [MCTS](https://github.com/tcconnally/MCTS). "
130+
"Run `mcts scan . --terminal-format github` for this output.*"
131+
)
132+
133+
console.print("\n".join(lines))

0 commit comments

Comments
 (0)