Skip to content
Open
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
4 changes: 4 additions & 0 deletions skills/corgea/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ corgea scan --sbom sbom.cdx.json # SBOM to a custom file
corgea scan --include-image myapp:1.2.3 # Also scan a fully built container image
corgea scan --include-image myapp:1.2.3 --include-image ghcr.io/acme/api:latest # Repeatable
corgea scan --project-name my-service # Override project name
corgea scan --project-naming-mode sparse-postfixed # One project per sparse-checkout service (monorepo-jsoar-agent)
corgea scan --skip-if-commit-scanned-recently # Reuse a recent scan of this commit instead of scanning again
corgea scan --skip-if-commit-scanned-recently --scanned-within 4h # Window for "recently" (default 24h)
corgea scan --skip-if-commit-scanned-recently --ignore-dirty-worktree # Reuse even if this tree or the prior scan is dirty
Expand All @@ -89,6 +90,8 @@ A pattern that matches the whole repository (`**`, `*`, `/**`) is refused, by bo

Include rules also take scan reuse off the table. A reusable scan ran before the rules existed, so its results cannot cover the files they force in — a run with any include rule (from the flag or the project) starts a real scan and prints `CORGEA_SCAN_SKIPPED=false`, as does a run whose rule lookup failed.

`--project-naming-mode sparse-postfixed` names the project for a monorepo service cloned with a sparse checkout: the default project name (the repository name) followed by the checked-out top-level folders, joined with dots — `monorepo-jsoar-agent`, or `monorepo-common.jsoar-agent` for two folders. A nested folder counts as its top-level folder (`services/payments` gives `monorepo-services`), characters outside `A-Za-z0-9._-` become `_`, and the name is cut at 100 characters. It only applies at the repository root of a sparse checkout; anywhere else it warns and the default name is used. It cannot be combined with `--project-name`. `upload`, `list` and `wait` take the same flag, so pass it there too to reach the same project.

`--only-uncommitted` and `--target` are mutually exclusive. `--fail-on`, `--fail`, and `--block-on` are mutually exclusive.

`--out-format`/`--out-file` and `--sbom` are honored regardless of the gate: the report and the SBOM are written before `--fail`/`--block-on` are evaluated, so a scan that exits 1 on a blocking rule still leaves the report file behind for the pipeline to ingest.
Expand Down Expand Up @@ -118,6 +121,7 @@ By default `upload` prints the scan page URL so you can track the results. Pass
```bash
corgea wait # Wait for latest scan
corgea wait SCAN_ID # Wait for a specific scan
corgea wait --project-naming-mode sparse-postfixed # Latest scan of the sparse-checkout service's project
```

Waiting (`corgea scan`, `corgea wait`, `corgea upload --wait`) exits 1 if the
Expand Down
52 changes: 50 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod list;
mod log;
mod manifest;
mod mcp;
mod project_naming;
mod scan;
mod setup_hooks;
mod skill;
Expand All @@ -29,9 +30,14 @@ mod targets;

use clap::{CommandFactory, Parser, Subcommand};
use config::Config;
use project_naming::ProjectNamingMode;
use scanners::fortify::parse as fortify_parse;
use std::str::FromStr;

const PROJECT_NAMING_MODE_HELP: &str = "Derive the Corgea project name instead of passing --project-name. sparse-postfixed: in a sparse checkout at the repository root, the checked-out top-level folders are appended to the default project name (monorepo-jsoar-agent for a checkout of jsoar-agent from monorepo), so each service of a monorepo gets its own project. Anywhere else it warns and the default name is used.";

const PROJECT_NAMING_MODE_QUERY_HELP: &str = "Query the project that a scan with the same --project-naming-mode names. sparse-postfixed: in a sparse checkout at the repository root, the default project name followed by the checked-out top-level folders (monorepo-jsoar-agent). Anywhere else it warns and the project is resolved as usual.";

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
Expand Down Expand Up @@ -78,6 +84,15 @@ enum Commands {
)]
project_name: Option<String>,

#[arg(
long,
value_enum,
value_name = "MODE",
conflicts_with = "project_name",
help = PROJECT_NAMING_MODE_HELP
)]
project_naming_mode: Option<ProjectNamingMode>,

#[arg(
long,
help = "Wait for the uploaded scan to complete and print the results. Without this flag, the command prints the scan page URL so you can track the results."
Expand Down Expand Up @@ -178,6 +193,15 @@ enum Commands {
)]
project_name: Option<String>,

#[arg(
long,
value_enum,
value_name = "MODE",
conflicts_with = "project_name",
help = PROJECT_NAMING_MODE_HELP
)]
project_naming_mode: Option<ProjectNamingMode>,

#[arg(
long,
value_name = "FILE",
Expand Down Expand Up @@ -224,6 +248,14 @@ enum Commands {
help = "Query this exact Corgea project name directly (skips repo auto-resolution)."
)]
project_name: Option<String>,
#[arg(
long,
value_enum,
value_name = "MODE",
conflicts_with_all = ["project_name", "repo"],
help = PROJECT_NAMING_MODE_QUERY_HELP
)]
project_naming_mode: Option<ProjectNamingMode>,
#[arg(
long,
help = "Resolve the project from this repo (org/repo slug or remote URL) instead of the git remote."
Expand Down Expand Up @@ -277,6 +309,15 @@ enum Commands {
)]
project_name: Option<String>,

#[arg(
long,
value_enum,
value_name = "MODE",
conflicts_with_all = ["project_name", "repo"],
help = PROJECT_NAMING_MODE_QUERY_HELP
)]
project_naming_mode: Option<ProjectNamingMode>,

#[arg(
long,
help = "Resolve the project from this repo (org/repo slug or remote URL) instead of the git remote."
Expand Down Expand Up @@ -689,9 +730,11 @@ fn main() {
Some(Commands::Upload {
report,
project_name,
project_naming_mode,
wait,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
let project_name = project_naming::resolve(project_name.clone(), *project_naming_mode);
let result = match report {
Some(report) => {
if report.ends_with(".fpr") {
Expand Down Expand Up @@ -737,6 +780,7 @@ fn main() {
exclude,
include,
project_name,
project_naming_mode,
sbom,
include_image,
skip_if_commit_scanned_recently,
Expand Down Expand Up @@ -915,6 +959,8 @@ fn main() {
None
};

let project_name = project_naming::resolve(project_name.clone(), *project_naming_mode);

match scanner {
Scanner::Snyk => scan::run_snyk(&corgea_config, project_name.clone()),
Scanner::Semgrep => scan::run_semgrep(&corgea_config, project_name.clone()),
Expand Down Expand Up @@ -945,6 +991,7 @@ fn main() {
Some(Commands::Wait {
scan_id,
project_name,
project_naming_mode,
repo,
project_id,
}) => {
Expand All @@ -954,7 +1001,7 @@ fn main() {
wait::WaitArgs {
scan_id: scan_id.clone(),
selector: utils::api::ProjectSelector {
name: project_name.clone(),
name: project_naming::resolve(project_name.clone(), *project_naming_mode),
repo: repo.clone(),
},
project_id: project_id.clone(),
Expand All @@ -970,6 +1017,7 @@ fn main() {
sca_issues,
code_quality,
project_name,
project_naming_mode,
repo,
}) => {
verify_token_and_exit_when_fail(&corgea_config);
Expand Down Expand Up @@ -999,7 +1047,7 @@ fn main() {
page_size: *page_size,
scan_id: scan_id.clone(),
selector: utils::api::ProjectSelector {
name: project_name.clone(),
name: project_naming::resolve(project_name.clone(), *project_naming_mode),
repo: repo.clone(),
},
},
Expand Down
Loading
Loading