Skip to content

Add --project-naming-mode=sparse-postfixed to name a project per sparse checkout - #188

Open
Ibrahimrahhal wants to merge 1 commit into
mainfrom
cursor/project-naming-mode-sparse-020d
Open

Ibrahimrahhal wants to merge 1 commit into
mainfrom
cursor/project-naming-mode-sparse-020d

Conversation

@Ibrahimrahhal

@Ibrahimrahhal Ibrahimrahhal commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

What

Adds --project-naming-mode <MODE> to scan, upload, list and wait. The only value is sparse-postfixed. It gives a monorepo service cloned with a sparse checkout its own Corgea project, without the pipeline having to compute a project name itself.

corgea scan --project-naming-mode sparse-postfixed
# Corgea project: monorepo-payments (default name 'monorepo' plus sparse checkout folders: payments)

Behaviour

  • Name: <default project name>-<folders joined with '.'>. The default name is the usual one: the repo name from origin, else the current folder name.
  • Folders: comments, negations and root-only patterns are dropped, and each pattern is cut to its top-level folder. A single trailing literal \n is stripped. Characters outside A-Za-z0-9._- become _, byte by byte, and the name is cut at 100 characters. This matches the naming CI pipelines already use for sparse checkouts, so their existing projects are kept.
  • When it applies: only at the worktree root of a sparse checkout. Anywhere else, or when no folder can be derived, it prints a warning and the command resolves the project as it does today.
  • Reading git state: sparse state is read through the git binary (git config --bool --default false core.sparseCheckout, then git sparse-checkout list, falling back to the info/sparse-checkout file). git sparse-checkout stores the setting in config.worktree, which is why git is asked rather than .git/config being read.
  • How the name is used: it is resolved once in main.rs and passed down exactly like --project-name. list/wait therefore query that project directly instead of resolving by repo URL. That resolution would fail once several projects share a repo.
  • Validation: clap rejects unknown values and lists the supported ones. The flag cannot be combined with --project-name (or with --repo on list/wait).

Behaviour without the flag is unchanged.

Known limitations (kept to match existing project names)

  • Nested folders reduce to their top-level folder: services/a and services/b both give <repo>-services.
  • Checking out a and b gives the same name as a folder called a.b.
  • Non-ASCII folder names are named from git's quoted form (café gives _caf_303_251_).

Tests

  • Unit tests in src/project_naming.rs cover pattern reduction, the character rule and length cap, and real sparse, full, root-only and subdirectory checkouts.
  • tests/project_naming_mode.rs covers, end to end: list querying the derived name in a sparse clone, falling back with a warning in a full clone, the conflict with --project-name on all four commands, and the unknown-value error.
  • ./harness check passes: strict clippy, formatting, 968 tests.
Open in Web Open in Cursor 

…se checkout

Co-authored-by: ibrahim <ibrahim@corgea.com>
@Ibrahimrahhal
Ibrahimrahhal marked this pull request as ready for review October 8, 2026 12:55
Comment thread src/project_naming.rs
return None;
}
let path = line.strip_prefix('/').unwrap_or(line);
let folder = path.split('/').next().unwrap_or_default();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high: Root files are incorrectly treated as top-level folders

top_level_folders assumes every literal first path segment is a directory. In non-cone mode, git sparse-checkout list can return exact root files such as package.json alongside service paths. This produces monorepo-package.json.services instead of monorepo-services, causing scan, upload, list, and wait to target a different project. Determine whether literal entries are directories from the repository tree, or explicitly reject/ignore file entries.

Proof or reproduction:

#[test]
fn root_files_do_not_become_project_suffixes() {
    assert_eq!(
        top_level_folders("package.json\n/services/**\n"),
        vec!["services"]
    );
}
// Current result: ["package.json", "services"]
// Current derived name: monorepo-package.json.services

@corgea-security corgea-security left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review risk: 3/5.

The naming mode can route scans and queries to the wrong project for valid non-cone sparse checkouts containing explicit root files.

Critical or high-priority changes must be addressed.

Automatic approval was not submitted: automated review found critical or high-priority findings.

@corgea-security corgea-security added the dennis-reviewed Dennis completed an automated review label Oct 8, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

git sparse-checkout list text is parsed as literal patterns, so a quoted path does not reduce to its top-level folder. ASCII cone checkouts (jsoar-agent, services/payments) are unaffected.

Open in Web View Automation 

Sent by Cursor Automation: pr-flow

Comment thread src/project_naming.rs
Comment on lines +109 to +111
let path = line.strip_prefix('/').unwrap_or(line);
let folder = path.split('/').next().unwrap_or_default();
(!folder.is_empty() && !folder.starts_with('*')).then(|| folder.to_string())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path.split('/') runs on the raw git sparse-checkout list line. On git 2.43 with the default core.quotePath=true, a cone checkout is printed as one C-quoted string, and the slash sits inside the quotes:

  • services/café lists as "services/caf\303\251". The first segment is "services, so the suffix is _services rather than services.
  • café/child lists as "caf\303\251/child". The first segment drops the closing quote and becomes _caf_303_251.
  • café itself stays "caf\303\251" → _caf_303_251_.

Those are three project names for one top-level folder. list and wait query that string directly, and scan uploads to it, with no warning. postfixed_name_applies_the_template_character_rule_to_folders only passes an already-extracted quoted token, so it never sees a slash inside the quotes.

core.quotePath=false (often set in ~/.gitconfig) makes list print raw UTF-8, so the same café checkout becomes caf__. This process does not pass -c core.quotePath=..., and the info/sparse-checkout fallback is raw UTF-8 too (/café/). The name then depends on git config and on which read path succeeds.

suggestion: list with git -c core.quotePath=false sparse-checkout list, take the first / segment of that raw path (and of the file fallback), then C-quote only that segment the way quotePath=true does (café → "caf\303\251", tab → \t, \\ and \" escaped, other bytes ≥ 0x80 or < 0x20 as \ooo) and run postfixed_name on that. Cover services/café and café/child with a real sparse checkout that expects services and "caf\303\251".

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dennis-reviewed Dennis completed an automated review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants