Skip to content

[Feature]: Add a --scope flag and automatic scope detection from monorepo workspace package names (Nx, Turborepo, pnpm workspaces) to improve commit message accuracy in multi-package repositories #75

Description

@divyanshim27

Summary

Gitbun's Conventional Commits output includes a scope field (e.g., feat(analyzer): ...). The current scope detection uses the primary modified directory name as the scope. In monorepos using Nx, Turborepo, or pnpm workspaces — where the actual package names differ from directory names — this produces incorrect or misleading scopes like feat(packages): ... instead of feat(ui-components): ....

Problem

  • The current scope detection reads the top-level modified directory (e.g., packages/ui-components/src/Button.tsx → scope: packages).
  • In a monorepo, packages is meaningless as a scope — the correct scope is ui-components (the workspace package name).
  • package.json files at workspace package roots contain the canonical name field (e.g., @myapp/ui-components) — this is never consulted during scope detection.
  • There is no --scope <name> flag to manually override scope detection when the automatic result is wrong.
  • Gitbun is a commit message tool primarily used by developers in complex codebases — monorepos are extremely common in that demographic (Next.js, NestJS, React Native projects, etc.).

Proposed Solution

1. Add a --scope CLI flag:

// src/cli/flags.ts
program
  .option('--scope <scope>', 'Manually set the conventional commit scope')

When --scope is provided, it bypasses all automatic scope detection entirely.

2. Improve automatic scope detection with workspace-aware logic:

// src/analyzer/scopeDetector.ts
import { readFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';

export function detectScope(modifiedFiles: string[]): string {
  // Walk up from the modified file's directory to find the nearest package.json
  for (const file of modifiedFiles) {
    let dir = dirname(file);
    while (dir !== '.' && dir !== '/') {
      const pkgPath = join(dir, 'package.json');
      if (existsSync(pkgPath)) {
        try {
          const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
          if (pkg.name) {
            // Strip org scope prefix: @myapp/ui-components → ui-components
            return pkg.name.replace(/^@[^/]+\//, '');
          }
        } catch {
          // malformed package.json — skip
        }
      }
      dir = dirname(dir);
    }
  }

  // Fallback: use the top-level directory name
  return modifiedFiles[0]?.split('/')[0] ?? 'core';
}

3. Handle pnpm/Turborepo workspace configs:

// Check pnpm-workspace.yaml or turbo.json to understand the monorepo structure
// and validate that the detected scope corresponds to a real workspace package

4. Interactive mode scope selection:
When Gitbun detects multiple workspace packages in the staged diff, the interactive preview will show a scope picker:

? Multiple packages modified. Which scope should this commit use?
❯ ui-components (3 files changed)
api-client (1 file changed)
shared-utils (2 files changed)
[custom scope...]

Additional Notes

  • The workspace-aware detection is entirely filesystem-based — no new dependencies are needed.
  • The --scope flag takes precedence over all automatic detection.
  • Detection falls back gracefully to the existing top-level directory logic if no package.json is found in the file tree.
  • I will add unit tests for scopeDetector.ts covering the monorepo case (nested package.json) and the fallback case.

Could you assign this issue to me?

Labels: enhancement, feature, cli, GSSoC 2026

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions