Skip to content

Commit fec60ad

Browse files
feat: add CI/CD integration and web dashboard visualization
- Added `codesage/reporters` module with Console, JSON, and GitHub PR reporters. - Added `codesage scan` CLI command for CI/CD pipelines. - Updated `codesage/web/server.py` to support snapshot upload and dashboard rendering. - Created `codesage/web/templates/dashboard.html` with Mermaid.js dependency graph visualization. - Added GitHub Actions workflow (`.github/workflows/codesnap_audit.yml`) and `action.yml`. - Updated README with CI integration instructions.
1 parent 88c1d00 commit fec60ad

File tree

16 files changed

+788
-13
lines changed

16 files changed

+788
-13
lines changed

.codesage/snapshots/index.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,5 +172,11 @@
172172
"timestamp": "2025-11-19T10:11:27.710277",
173173
"path": ".codesage/snapshots/v29.json",
174174
"git_commit": null
175+
},
176+
{
177+
"version": "v30",
178+
"timestamp": "2023-01-01T00:00:00",
179+
"path": ".codesage/snapshots/v30.json",
180+
"git_commit": null
175181
}
176182
]

.codesage/snapshots/v30.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"metadata":{"version":"v30","timestamp":"2023-01-01T00:00:00","project_name":"test_project","file_count":1,"total_size":100,"git_commit":null,"tool_version":"1.0.0","config_hash":"abc"},"files":[],"dependencies":null,"risk_summary":null,"issues_summary":null,"llm_stats":null,"languages":[],"language_stats":{},"global_metrics":null,"dependency_graph":null,"detected_patterns":[],"issues":[]}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CodeSnapAI Security Audit
2+
on:
3+
pull_request:
4+
types: [opened, synchronize, reopened]
5+
workflow_dispatch:
6+
7+
jobs:
8+
audit:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
pull-requests: write
13+
checks: write
14+
issues: write
15+
steps:
16+
- name: Checkout Code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.10'
23+
24+
- name: Run CodeSnapAI
25+
uses: ./
26+
with:
27+
target: "."
28+
language: "python"
29+
fail_on_high: "true"
30+
env:
31+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -241,23 +241,30 @@ codesage report \
241241

242242
### Example 1: CI/CD Integration
243243

244+
You can easily integrate CodeSnapAI into your GitHub Actions workflow using our official action.
245+
244246
```yaml
245-
# .github/workflows/code-quality.yml
246-
name: Code Quality Gate
247+
# .github/workflows/codesnap_audit.yml
248+
name: CodeSnapAI Security Audit
247249
on: [pull_request]
248250
249251
jobs:
250-
complexity-check:
252+
audit:
251253
runs-on: ubuntu-latest
254+
permissions:
255+
contents: read
256+
pull-requests: write
257+
checks: write
252258
steps:
253-
- uses: actions/checkout@v3
254-
- name: Install CodeSnapAI
255-
run: pip install codesage
256-
257-
- name: Complexity Analysis
258-
run: |
259-
codesage scan . --threshold cyclomatic=12 --output report.json
260-
codesage gate report.json --max-violations 5
259+
- uses: actions/checkout@v4
260+
- name: Run CodeSnapAI
261+
uses: turtacn/CodeSnapAI@main # Replace with tagged version in production
262+
with:
263+
target: "."
264+
language: "python"
265+
fail_on_high: "true"
266+
env:
267+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
261268
```
262269

263270
### Example 2: Python Library Usage

action.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: 'CodeSnapAI'
2+
description: 'Intelligent Code Analysis & Governance Tool'
3+
inputs:
4+
target:
5+
description: 'Path to scan'
6+
required: true
7+
default: '.'
8+
language:
9+
description: 'Language to analyze (python, go, shell)'
10+
required: false
11+
default: 'python'
12+
reporter:
13+
description: 'Reporter to use (console, json, github)'
14+
required: false
15+
default: 'github'
16+
fail_on_high:
17+
description: 'Fail if high severity issues are found'
18+
required: false
19+
default: 'false'
20+
21+
runs:
22+
using: "composite"
23+
steps:
24+
- name: Install Dependencies
25+
shell: bash
26+
run: |
27+
pip install poetry
28+
poetry install --only main
29+
30+
- name: Run Scan
31+
shell: bash
32+
run: |
33+
ARGS=""
34+
if [ "${{ inputs.fail_on_high }}" == "true" ]; then
35+
ARGS="$ARGS --fail-on-high"
36+
fi
37+
38+
# We assume the action is running in the repo root where codesage is available or installed
39+
# If installed via pip/poetry, we run it.
40+
41+
poetry run codesage scan ${{ inputs.target }} \
42+
--language ${{ inputs.language }} \
43+
--reporter ${{ inputs.reporter }} \
44+
--ci-mode \
45+
$ARGS

codesage/cli/commands/scan.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import click
2+
import os
3+
import sys
4+
from pathlib import Path
5+
from typing import Optional
6+
7+
from codesage.semantic_digest.python_snapshot_builder import PythonSemanticSnapshotBuilder, SnapshotConfig
8+
from codesage.semantic_digest.go_snapshot_builder import GoSemanticSnapshotBuilder
9+
from codesage.semantic_digest.shell_snapshot_builder import ShellSemanticSnapshotBuilder
10+
from codesage.snapshot.models import ProjectSnapshot
11+
from codesage.reporters import ConsoleReporter, JsonReporter, GitHubPRReporter
12+
13+
def get_builder(language: str, path: Path):
14+
config = SnapshotConfig()
15+
if language == 'python':
16+
return PythonSemanticSnapshotBuilder(path, config)
17+
elif language == 'go':
18+
return GoSemanticSnapshotBuilder(path, config)
19+
elif language == 'shell':
20+
return ShellSemanticSnapshotBuilder(path, config)
21+
else:
22+
return None
23+
24+
@click.command('scan')
25+
@click.argument('path', type=click.Path(exists=True, dir_okay=True))
26+
@click.option('--language', '-l', type=click.Choice(['python', 'go', 'shell']), default='python', help='Language to analyze.')
27+
@click.option('--reporter', '-r', type=click.Choice(['console', 'json', 'github']), default='console', help='Reporter to use.')
28+
@click.option('--output', '-o', help='Output path for JSON reporter.')
29+
@click.option('--fail-on-high', is_flag=True, help='Exit with non-zero code if high severity issues are found.')
30+
@click.option('--ci-mode', is_flag=True, help='Enable CI mode (auto-detect GitHub environment).')
31+
@click.pass_context
32+
def scan(ctx, path, language, reporter, output, fail_on_high, ci_mode):
33+
"""
34+
Scan the codebase and report issues.
35+
"""
36+
click.echo(f"Scanning {path} for {language}...")
37+
38+
root_path = Path(path)
39+
builder = get_builder(language, root_path)
40+
41+
if not builder:
42+
click.echo(f"Unsupported language: {language}", err=True)
43+
ctx.exit(1)
44+
45+
try:
46+
snapshot: ProjectSnapshot = builder.build()
47+
except Exception as e:
48+
click.echo(f"Scan failed: {e}", err=True)
49+
ctx.exit(1)
50+
51+
# Select Reporter
52+
reporters = []
53+
54+
# Always add console reporter unless we are in json mode only?
55+
# Usually CI logs want console output too.
56+
if reporter == 'console':
57+
reporters.append(ConsoleReporter())
58+
elif reporter == 'json':
59+
out_path = output or "codesage_report.json"
60+
reporters.append(JsonReporter(output_path=out_path))
61+
elif reporter == 'github':
62+
reporters.append(ConsoleReporter()) # Still print to console
63+
64+
# Check environment
65+
token = os.environ.get("GITHUB_TOKEN")
66+
repo = os.environ.get("GITHUB_REPOSITORY")
67+
68+
# Try to get PR number
69+
pr_number = None
70+
ref = os.environ.get("GITHUB_REF") # refs/pull/123/merge
71+
if ref and "pull" in ref:
72+
try:
73+
pr_number = int(ref.split("/")[2])
74+
except (IndexError, ValueError):
75+
pass
76+
77+
# Or from event.json
78+
event_path = os.environ.get("GITHUB_EVENT_PATH")
79+
if not pr_number and event_path and os.path.exists(event_path):
80+
import json
81+
try:
82+
with open(event_path) as f:
83+
event = json.load(f)
84+
pr_number = event.get("pull_request", {}).get("number")
85+
except Exception:
86+
pass
87+
88+
if token and repo and pr_number:
89+
reporters.append(GitHubPRReporter(token=token, repo=repo, pr_number=pr_number))
90+
else:
91+
click.echo("GitHub reporter selected but missing environment variables (GITHUB_TOKEN, GITHUB_REPOSITORY) or not in a PR context.", err=True)
92+
93+
# CI Mode overrides
94+
if ci_mode and os.environ.get("GITHUB_ACTIONS") == "true":
95+
# In CI mode, we might force certain reporters or behavior
96+
pass
97+
98+
# Execute Reporters
99+
for r in reporters:
100+
r.report(snapshot)
101+
102+
# Check Fail Condition
103+
if fail_on_high:
104+
has_high_risk = False
105+
if snapshot.issues_summary:
106+
if snapshot.issues_summary.by_severity.get('high', 0) > 0 or \
107+
snapshot.issues_summary.by_severity.get('error', 0) > 0:
108+
has_high_risk = True
109+
110+
# Also check risk summary if issues are not populated but risk is
111+
if snapshot.risk_summary and snapshot.risk_summary.high_risk_files > 0:
112+
has_high_risk = True
113+
114+
if has_high_risk:
115+
click.echo("Failure: High risk issues detected.", err=True)
116+
ctx.exit(1)
117+
118+
click.echo("Scan finished successfully.")

codesage/cli/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Placeholder for commands
55
from .commands.analyze import analyze
66
from .commands.snapshot import snapshot
7+
from .commands.scan import scan
78
from .commands.diff import diff
89
from .commands.config import config
910
from .commands.report import report
@@ -49,6 +50,7 @@ def main(ctx, config_path, verbose, no_color):
4950

5051
main.add_command(analyze)
5152
main.add_command(snapshot)
53+
main.add_command(scan)
5254
main.add_command(diff)
5355
main.add_command(config)
5456
main.add_command(report)

codesage/reporters/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .console import ConsoleReporter
2+
from .json_reporter import JsonReporter
3+
from .github_pr import GitHubPRReporter
4+
5+
__all__ = ["ConsoleReporter", "JsonReporter", "GitHubPRReporter"]

codesage/reporters/base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from abc import ABC, abstractmethod
2+
from typing import List, Optional
3+
from codesage.snapshot.models import ProjectSnapshot
4+
5+
class BaseReporter(ABC):
6+
@abstractmethod
7+
def report(self, snapshot: ProjectSnapshot) -> None:
8+
"""
9+
Report the findings of the snapshot.
10+
11+
Args:
12+
snapshot: The project snapshot containing analysis results and issues.
13+
"""
14+
pass

codesage/reporters/console.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from .base import BaseReporter
2+
from codesage.snapshot.models import ProjectSnapshot
3+
import click
4+
5+
class ConsoleReporter(BaseReporter):
6+
def report(self, snapshot: ProjectSnapshot) -> None:
7+
click.echo("Scan Complete")
8+
9+
# Summary
10+
click.echo("-" * 40)
11+
click.echo(f"Project: {snapshot.metadata.project_name}")
12+
click.echo(f"Files Scanned: {len(snapshot.files)}")
13+
14+
if snapshot.issues_summary:
15+
click.echo(f"Total Issues: {snapshot.issues_summary.total_issues}")
16+
for severity, count in snapshot.issues_summary.by_severity.items():
17+
click.echo(f" {severity.upper()}: {count}")
18+
else:
19+
click.echo("No issues summary available.")
20+
21+
if snapshot.risk_summary:
22+
click.echo(f"Risk Score: {snapshot.risk_summary.avg_risk:.2f}")
23+
click.echo(f"High Risk Files: {snapshot.risk_summary.high_risk_files}")
24+
25+
click.echo("-" * 40)
26+
27+
# Detail for High/Error issues
28+
if snapshot.issues_summary and snapshot.issues_summary.total_issues > 0:
29+
click.echo("\nTop Issues:")
30+
count = 0
31+
for file in snapshot.files:
32+
for issue in file.issues:
33+
if issue.severity in ['error', 'warning', 'high']:
34+
click.echo(f"[{issue.severity.upper()}] {file.path}:{issue.location.line} - {issue.message}")
35+
count += 1
36+
if count >= 10:
37+
click.echo("... and more")
38+
return

0 commit comments

Comments
 (0)