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
6 changes: 6 additions & 0 deletions .codesage/snapshots/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,11 @@
"timestamp": "2025-11-19T10:11:27.710277",
"path": ".codesage/snapshots/v29.json",
"git_commit": null
},
{
"version": "v30",
"timestamp": "2023-01-01T00:00:00",
"path": ".codesage/snapshots/v30.json",
"git_commit": null
}
]
1 change: 1 addition & 0 deletions .codesage/snapshots/v30.json
Original file line number Diff line number Diff line change
@@ -0,0 +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":[]}
31 changes: 31 additions & 0 deletions .github/workflows/codesnap_audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CodeSnapAI Security Audit
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:

jobs:
audit:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
checks: write
issues: write
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'

- name: Run CodeSnapAI
uses: ./
with:
target: "."
language: "python"
fail_on_high: "true"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,23 +241,30 @@ codesage report \

### Example 1: CI/CD Integration

You can easily integrate CodeSnapAI into your GitHub Actions workflow using our official action.

```yaml
# .github/workflows/code-quality.yml
name: Code Quality Gate
# .github/workflows/codesnap_audit.yml
name: CodeSnapAI Security Audit
on: [pull_request]

jobs:
complexity-check:
audit:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
checks: write
steps:
- uses: actions/checkout@v3
- name: Install CodeSnapAI
run: pip install codesage

- name: Complexity Analysis
run: |
codesage scan . --threshold cyclomatic=12 --output report.json
codesage gate report.json --max-violations 5
- uses: actions/checkout@v4
- name: Run CodeSnapAI
uses: turtacn/CodeSnapAI@main # Replace with tagged version in production
with:
target: "."
language: "python"
fail_on_high: "true"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

### Example 2: Python Library Usage
Expand Down
45 changes: 45 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: 'CodeSnapAI'
description: 'Intelligent Code Analysis & Governance Tool'
inputs:
target:
description: 'Path to scan'
required: true
default: '.'
language:
description: 'Language to analyze (python, go, shell)'
required: false
default: 'python'
reporter:
description: 'Reporter to use (console, json, github)'
required: false
default: 'github'
fail_on_high:
description: 'Fail if high severity issues are found'
required: false
default: 'false'

runs:
using: "composite"
steps:
- name: Install Dependencies
shell: bash
run: |
pip install poetry
poetry install --only main

- name: Run Scan
shell: bash
run: |
ARGS=""
if [ "${{ inputs.fail_on_high }}" == "true" ]; then
ARGS="$ARGS --fail-on-high"
fi

# We assume the action is running in the repo root where codesage is available or installed
# If installed via pip/poetry, we run it.

poetry run codesage scan ${{ inputs.target }} \
--language ${{ inputs.language }} \
--reporter ${{ inputs.reporter }} \
--ci-mode \
$ARGS
118 changes: 118 additions & 0 deletions codesage/cli/commands/scan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import click
import os
import sys
from pathlib import Path
from typing import Optional

from codesage.semantic_digest.python_snapshot_builder import PythonSemanticSnapshotBuilder, SnapshotConfig
from codesage.semantic_digest.go_snapshot_builder import GoSemanticSnapshotBuilder
from codesage.semantic_digest.shell_snapshot_builder import ShellSemanticSnapshotBuilder
from codesage.snapshot.models import ProjectSnapshot
from codesage.reporters import ConsoleReporter, JsonReporter, GitHubPRReporter

def get_builder(language: str, path: Path):
config = SnapshotConfig()
if language == 'python':
return PythonSemanticSnapshotBuilder(path, config)
elif language == 'go':
return GoSemanticSnapshotBuilder(path, config)
elif language == 'shell':
return ShellSemanticSnapshotBuilder(path, config)
else:
return None

@click.command('scan')
@click.argument('path', type=click.Path(exists=True, dir_okay=True))
@click.option('--language', '-l', type=click.Choice(['python', 'go', 'shell']), default='python', help='Language to analyze.')
@click.option('--reporter', '-r', type=click.Choice(['console', 'json', 'github']), default='console', help='Reporter to use.')
@click.option('--output', '-o', help='Output path for JSON reporter.')
@click.option('--fail-on-high', is_flag=True, help='Exit with non-zero code if high severity issues are found.')
@click.option('--ci-mode', is_flag=True, help='Enable CI mode (auto-detect GitHub environment).')
@click.pass_context
def scan(ctx, path, language, reporter, output, fail_on_high, ci_mode):
"""
Scan the codebase and report issues.
"""
click.echo(f"Scanning {path} for {language}...")

root_path = Path(path)
builder = get_builder(language, root_path)

if not builder:
click.echo(f"Unsupported language: {language}", err=True)
ctx.exit(1)

try:
snapshot: ProjectSnapshot = builder.build()
except Exception as e:
click.echo(f"Scan failed: {e}", err=True)
ctx.exit(1)

# Select Reporter
reporters = []

# Always add console reporter unless we are in json mode only?
# Usually CI logs want console output too.
if reporter == 'console':
reporters.append(ConsoleReporter())
elif reporter == 'json':
out_path = output or "codesage_report.json"
reporters.append(JsonReporter(output_path=out_path))
elif reporter == 'github':
reporters.append(ConsoleReporter()) # Still print to console

# Check environment
token = os.environ.get("GITHUB_TOKEN")
repo = os.environ.get("GITHUB_REPOSITORY")

# Try to get PR number
pr_number = None
ref = os.environ.get("GITHUB_REF") # refs/pull/123/merge
if ref and "pull" in ref:
try:
pr_number = int(ref.split("/")[2])
except (IndexError, ValueError):
pass

# Or from event.json
event_path = os.environ.get("GITHUB_EVENT_PATH")
if not pr_number and event_path and os.path.exists(event_path):
import json
try:
with open(event_path) as f:
event = json.load(f)
pr_number = event.get("pull_request", {}).get("number")
except Exception:
pass

if token and repo and pr_number:
reporters.append(GitHubPRReporter(token=token, repo=repo, pr_number=pr_number))
else:
click.echo("GitHub reporter selected but missing environment variables (GITHUB_TOKEN, GITHUB_REPOSITORY) or not in a PR context.", err=True)

# CI Mode overrides
if ci_mode and os.environ.get("GITHUB_ACTIONS") == "true":
# In CI mode, we might force certain reporters or behavior
pass

# Execute Reporters
for r in reporters:
r.report(snapshot)

# Check Fail Condition
if fail_on_high:
has_high_risk = False
if snapshot.issues_summary:
if snapshot.issues_summary.by_severity.get('high', 0) > 0 or \
snapshot.issues_summary.by_severity.get('error', 0) > 0:
has_high_risk = True

# Also check risk summary if issues are not populated but risk is
if snapshot.risk_summary and snapshot.risk_summary.high_risk_files > 0:
has_high_risk = True

if has_high_risk:
click.echo("Failure: High risk issues detected.", err=True)
ctx.exit(1)

click.echo("Scan finished successfully.")
2 changes: 2 additions & 0 deletions codesage/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Placeholder for commands
from .commands.analyze import analyze
from .commands.snapshot import snapshot
from .commands.scan import scan
from .commands.diff import diff
from .commands.config import config
from .commands.report import report
Expand Down Expand Up @@ -49,6 +50,7 @@ def main(ctx, config_path, verbose, no_color):

main.add_command(analyze)
main.add_command(snapshot)
main.add_command(scan)
main.add_command(diff)
main.add_command(config)
main.add_command(report)
Expand Down
5 changes: 5 additions & 0 deletions codesage/reporters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .console import ConsoleReporter
from .json_reporter import JsonReporter
from .github_pr import GitHubPRReporter

__all__ = ["ConsoleReporter", "JsonReporter", "GitHubPRReporter"]
14 changes: 14 additions & 0 deletions codesage/reporters/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from abc import ABC, abstractmethod
from typing import List, Optional
from codesage.snapshot.models import ProjectSnapshot

class BaseReporter(ABC):
@abstractmethod
def report(self, snapshot: ProjectSnapshot) -> None:
"""
Report the findings of the snapshot.

Args:
snapshot: The project snapshot containing analysis results and issues.
"""
pass
38 changes: 38 additions & 0 deletions codesage/reporters/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from .base import BaseReporter
from codesage.snapshot.models import ProjectSnapshot
import click

class ConsoleReporter(BaseReporter):
def report(self, snapshot: ProjectSnapshot) -> None:
click.echo("Scan Complete")

# Summary
click.echo("-" * 40)
click.echo(f"Project: {snapshot.metadata.project_name}")
click.echo(f"Files Scanned: {len(snapshot.files)}")

if snapshot.issues_summary:
click.echo(f"Total Issues: {snapshot.issues_summary.total_issues}")
for severity, count in snapshot.issues_summary.by_severity.items():
click.echo(f" {severity.upper()}: {count}")
else:
click.echo("No issues summary available.")

if snapshot.risk_summary:
click.echo(f"Risk Score: {snapshot.risk_summary.avg_risk:.2f}")
click.echo(f"High Risk Files: {snapshot.risk_summary.high_risk_files}")

click.echo("-" * 40)

# Detail for High/Error issues
if snapshot.issues_summary and snapshot.issues_summary.total_issues > 0:
click.echo("\nTop Issues:")
count = 0
for file in snapshot.files:
for issue in file.issues:
if issue.severity in ['error', 'warning', 'high']:
click.echo(f"[{issue.severity.upper()}] {file.path}:{issue.location.line} - {issue.message}")
count += 1
if count >= 10:
click.echo("... and more")
return
Loading
Loading