Skip to content

feat: Plan-aware nested repo branching for multi-module workspaces#2121

Open
sakitA wants to merge 4 commits intogithub:mainfrom
sakitA:feat/nested-repo-branching
Open

feat: Plan-aware nested repo branching for multi-module workspaces#2121
sakitA wants to merge 4 commits intogithub:mainfrom
sakitA:feat/nested-repo-branching

Conversation

@sakitA
Copy link
Copy Markdown
Contributor

@sakitA sakitA commented Apr 8, 2026

Summary

Adds support for nested independent git repositories in multi-module workspaces. Sub-components that live under the root workspace with their own .git but are not formal git submodules.

Instead of automatically branching all nested repos during feature creation, this PR implements a plan-aware, task-driven workflow where the AI agent identifies affected repos from the spec and generates targeted branch-creation tasks.

Resolves #2120

Problem

In multi-module projects where sub-components maintain independent git histories (nested under a shared root), spec-kit only operates on the root repo. Developers must manually create matching feature branches in each affected sub-component.

Workflow

Phase What Happens
Specify Creates root branch + spec only
Plan setup-plan.sh/.ps1 discovers nested repos (NESTED_REPOS in JSON). AI identifies affected repos from spec
Tasks Generates Phase 1 setup task for branching only the affected nested repos
Implement Executes the branch-creation task via git commands

Design Decisions

  • Discovery in plan phase, not specify: At specify time the spec does not exist yet
  • AI-driven selection: AI agent determines affected repos by analyzing spec against discovered repos
  • Configurable depth: --scan-depth N / -ScanDepth N controls scan depth (default: 2)
  • Non-blocking: Discovery failures produce warnings, do not abort
  • Backward-compatible: Single-repo projects see zero behavior change

Changes

File Change
scripts/bash/common.sh ind_nested_git_repos() recursive discovery in subshell
scripts/bash/setup-plan.sh --scan-depth flag with validation, NESTED_REPOS in JSON
scripts/powershell/common.ps1 Find-NestedGitRepos with -MaxDepth param
scripts/powershell/setup-plan.ps1 -ScanDepth param, NESTED_REPOS in JSON
emplates/commands/plan.md Step 3: AI identifies affected repos from spec
emplates/plan-template.md Affected Nested Repositories section
emplates/commands/tasks.md Guidance for branch-creation setup task
emplates/tasks-template.md Phase 1 example for nested repo branching
emplates/commands/specify.md Note pointing to plan+tasks
ests/test_nested_repos.py 13 tests covering full workflow

Testing

  • 13 new tests, all passing
  • Covers: discovery, configurable depth, setup-plan JSON output, verify specify does NOT branch

AI Disclosure

This contribution was made with AI assistance (GitHub Copilot), as required by CONTRIBUTING.md.

… git repos

Add auto-detection of nested independent git repositories (not submodules)
and create matching feature branches in each when running create-new-feature.

Changes:
- common.sh: Add find_nested_git_repos() - discovers nested repos up to 2 levels deep
- common.ps1: Add Find-NestedGitRepos - PowerShell equivalent
- create-new-feature.sh: Create feature branches in all nested repos after root
- create-new-feature.ps1: Same for PowerShell
- specify.md: Document NESTED_REPOS JSON output field
- test_nested_repos.py: 9 tests covering discovery and branch creation

Design decisions:
- Auto-detect via .git presence (no configuration required)
- Non-blocking: nested repo failures are warnings, not errors
- JSON output extended with NESTED_REPOS array [{path, status}]
- Backward-compatible: no change for single-repo workflows

Resolves github#2120
Related to github#1050

Note: This contribution was made with AI assistance (GitHub Copilot).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sakitA sakitA requested a review from mnriem as a code owner April 8, 2026 07:37
Copilot AI review requested due to automatic review settings April 8, 2026 07:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds coordinated feature-branch creation for workspaces that contain nested, independent git repositories (not formal submodules), so create-new-feature can create/switch the same feature branch across the root repo and detected nested repos and report results in JSON.

Changes:

  • Add nested repo discovery helpers (find_nested_git_repos / Find-NestedGitRepos) scanning up to 2 levels deep with a skip list.
  • Create/switch matching branches in detected nested repos from both Bash and PowerShell create-new-feature scripts, and emit NESTED_REPOS in JSON output.
  • Document NESTED_REPOS and add pytest coverage for Bash discovery/branching behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
scripts/bash/common.sh Adds nested git repo discovery helper for Bash.
scripts/bash/create-new-feature.sh Creates matching branches in discovered nested repos and includes NESTED_REPOS in JSON output.
scripts/powershell/common.ps1 Adds nested git repo discovery helper for PowerShell.
scripts/powershell/create-new-feature.ps1 Creates matching branches in discovered nested repos and includes NESTED_REPOS in JSON output.
templates/commands/specify.md Documents the new NESTED_REPOS JSON field and behavior.
tests/test_nested_repos.py Adds tests for nested repo discovery + Bash branching + JSON output.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +267 to +273
_should_skip() {
local name="$1"
for skip in "${skip_dirs[@]}"; do
[ "$name" = "$skip" ] && return 0
done
return 1
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

In find_nested_git_repos, the helper _should_skip() is defined inside the function but becomes a global function in bash (functions aren't scoped). Because it relies on dynamically-scoped skip_dirs, calling it outside this function (or redefining it elsewhere) can lead to hard-to-debug behavior. Consider replacing this with a case/[[ ... ]] check inline, or use a uniquely-named helper and unset -f it before returning.

Copilot uses AI. Check for mistakes.
Comment on lines +288 to +303
else
# Level 2
for grandchild in "$child"/*/; do
[ -d "$grandchild" ] || continue
grandchild="${grandchild%/}"
local gc_name
gc_name="$(basename "$grandchild")"
_should_skip "$gc_name" && continue

if [ -e "$grandchild/.git" ]; then
if git -C "$grandchild" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$grandchild"
fi
fi
done
fi
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

find_nested_git_repos only scans “level 2” when the level-1 directory does not have a .git marker. That means a repo at $repo_root/child/.git will prevent discovery of another repo at $repo_root/child/grandchild/.git, even though it's still within 2 levels of the root. If the intent is truly “scan up to 2 levels deep”, the level-2 traversal should run regardless (while still applying the skip list).

Suggested change
else
# Level 2
for grandchild in "$child"/*/; do
[ -d "$grandchild" ] || continue
grandchild="${grandchild%/}"
local gc_name
gc_name="$(basename "$grandchild")"
_should_skip "$gc_name" && continue
if [ -e "$grandchild/.git" ]; then
if git -C "$grandchild" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$grandchild"
fi
fi
done
fi
fi
# Level 2
for grandchild in "$child"/*/; do
[ -d "$grandchild" ] || continue
grandchild="${grandchild%/}"
local gc_name
gc_name="$(basename "$grandchild")"
_should_skip "$gc_name" && continue
if [ -e "$grandchild/.git" ]; then
if git -C "$grandchild" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$grandchild"
fi
fi
done

Copilot uses AI. Check for mistakes.
Comment on lines +378 to +383
while IFS= read -r nested_path; do
[ -z "$nested_path" ] && continue
# Normalize: remove trailing slash
nested_path="${nested_path%/}"
# Compute relative path for output
rel_path="${nested_path#"$REPO_ROOT/"}"
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

rel_path="${nested_path#"$REPO_ROOT/"}" uses bash pattern matching in parameter expansion, so repo roots containing glob characters (e.g. [ or *) can produce incorrect stripping and leak absolute paths into NESTED_REPOS.path. A safer approach is to compute the relative path by string length (only after verifying the prefix), or delegate to a tool like python/realpath for literal path handling.

Suggested change
while IFS= read -r nested_path; do
[ -z "$nested_path" ] && continue
# Normalize: remove trailing slash
nested_path="${nested_path%/}"
# Compute relative path for output
rel_path="${nested_path#"$REPO_ROOT/"}"
repo_root_prefix="${REPO_ROOT%/}/"
while IFS= read -r nested_path; do
[ -z "$nested_path" ] && continue
# Normalize: remove trailing slash
nested_path="${nested_path%/}"
# Compute relative path for output using literal prefix handling
if [ "${nested_path:0:${#repo_root_prefix}}" = "$repo_root_prefix" ]; then
rel_path="${nested_path:${#repo_root_prefix}}"
else
rel_path="$nested_path"
fi

Copilot uses AI. Check for mistakes.
Comment on lines +230 to +244
} else {
# Level 2
$grandchildren = Get-ChildItem -Path $child.FullName -Directory -ErrorAction SilentlyContinue |
Where-Object { $skipDirs -notcontains $_.Name }

foreach ($gc in $grandchildren) {
$gcGitMarker = Join-Path $gc.FullName '.git'
if (Test-Path -LiteralPath $gcGitMarker) {
try {
$null = git -C $gc.FullName rev-parse --is-inside-work-tree 2>$null
if ($LASTEXITCODE -eq 0) {
$results += $gc.FullName
}
} catch { }
}
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

Find-NestedGitRepos only descends to the second directory level when the level-1 directory does not contain a .git marker. This skips repos that are still within 2 levels of $RepoRoot but happen to be nested under a level-1 repo. If the intended behavior is “scan up to 2 levels deep”, consider always scanning grandchildren (while still excluding .git / other skip dirs).

Suggested change
} else {
# Level 2
$grandchildren = Get-ChildItem -Path $child.FullName -Directory -ErrorAction SilentlyContinue |
Where-Object { $skipDirs -notcontains $_.Name }
foreach ($gc in $grandchildren) {
$gcGitMarker = Join-Path $gc.FullName '.git'
if (Test-Path -LiteralPath $gcGitMarker) {
try {
$null = git -C $gc.FullName rev-parse --is-inside-work-tree 2>$null
if ($LASTEXITCODE -eq 0) {
$results += $gc.FullName
}
} catch { }
}
}
# Level 2
$grandchildren = Get-ChildItem -Path $child.FullName -Directory -ErrorAction SilentlyContinue |
Where-Object { $skipDirs -notcontains $_.Name }
foreach ($gc in $grandchildren) {
$gcGitMarker = Join-Path $gc.FullName '.git'
if (Test-Path -LiteralPath $gcGitMarker) {
try {
$null = git -C $gc.FullName rev-parse --is-inside-work-tree 2>$null
if ($LASTEXITCODE -eq 0) {
$results += $gc.FullName
}
} catch { }

Copilot uses AI. Check for mistakes.
if ($hasGit) {
$nestedRepos = Find-NestedGitRepos -RepoRoot $repoRoot
foreach ($nestedPath in $nestedRepos) {
$relPath = $nestedPath.Substring($repoRoot.Length).TrimStart('\', '/')
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

$relPath = $nestedPath.Substring($repoRoot.Length).TrimStart(...) assumes $nestedPath always starts with the exact $repoRoot string (same casing, no trailing separator differences). Using [System.IO.Path]::GetRelativePath($repoRoot, $nestedPath) (or Resolve-Path normalization + a safer relative-path helper) would avoid subtle path mismatches and make output paths more reliable across platforms/filesystems.

Suggested change
$relPath = $nestedPath.Substring($repoRoot.Length).TrimStart('\', '/')
$relPath = [System.IO.Path]::GetRelativePath($repoRoot, $nestedPath)

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +11
Pytest tests for nested independent git repository support in create-new-feature scripts.

Tests cover:
- Discovery of nested git repos via find_nested_git_repos (bash) / Find-NestedGitRepos (PS)
- Branch creation in nested repos during feature creation
- JSON output includes NESTED_REPOS field
- --dry-run reports nested repos without creating branches
- --allow-existing-branch propagates to nested repos
- Excluded directories are skipped
- Graceful handling when nested repos cannot be branched
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This test module’s header says it covers PowerShell discovery (Find-NestedGitRepos) and “graceful handling when nested repos cannot be branched”, but there are currently no tests exercising the PowerShell scripts or a nested-branch failure scenario (e.g., dirty worktree causing checkout to fail). Either add those cases (similar to tests/test_timestamp_branches.py’s conditional pwsh runner), or adjust the docstring/scope so it matches what’s actually being validated.

Suggested change
Pytest tests for nested independent git repository support in create-new-feature scripts.
Tests cover:
- Discovery of nested git repos via find_nested_git_repos (bash) / Find-NestedGitRepos (PS)
- Branch creation in nested repos during feature creation
- JSON output includes NESTED_REPOS field
- --dry-run reports nested repos without creating branches
- --allow-existing-branch propagates to nested repos
- Excluded directories are skipped
- Graceful handling when nested repos cannot be branched
Pytest tests for nested independent git repository support in the Bash
create-new-feature script.
Tests cover:
- Discovery of nested git repos via find_nested_git_repos (bash)
- Branch creation in nested repos during feature creation
- JSON output includes NESTED_REPOS field
- --dry-run reports nested repos without creating branches
- --allow-existing-branch propagates to nested repos
- Excluded directories are skipped

Copilot uses AI. Check for mistakes.
sakitA and others added 2 commits April 8, 2026 00:49
Enhance nested repo support with two key improvements:

1. Configurable scan depth:
   - find_nested_git_repos() / Find-NestedGitRepos now accept a depth parameter
   - Uses recursive traversal instead of hardcoded 2-level nesting
   - New --scan-depth (Bash) / -ScanDepth (PowerShell) flag
   - Reads nested_repo_scan_depth from init-options.json

2. Selective branching via --repos flag:
   - New --repos (Bash) / -Repos (PowerShell) flag accepts comma-separated paths
   - Only branches repos matching the specified paths
   - Omit flag to branch all discovered repos (backward compatible)
   - Enables spec-driven branching: AI agents select relevant repos per feature

Tests: 17 tests (8 new for depth + filtering), all passing.
Docs: Updated specify.md with --repos, --scan-depth, and init-options.json config.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of automatically creating branches in all nested repos during
/speckit.specify, this redesign defers branching to the plantasksimplement
workflow:

- Specify phase: creates root branch + spec only (no nested repo work)
- Plan phase: setup-plan.sh discovers nested repos (NESTED_REPOS in JSON)
  and the AI agent identifies affected modules from the spec
- Tasks phase: generates a Phase 1 setup task for creating feature branches
  in only the affected nested repos identified by the plan
- Implement phase: executes the branch-creation task via git commands

Changes:
- Revert create-new-feature.sh/.ps1: remove --repos, --scan-depth, and
  NESTED_REPOS output (specify phase is clean again)
- Add nested repo discovery to setup-plan.sh/.ps1 with --scan-depth flag
- Update plan.md template: add step for AI to identify affected repos
- Update plan-template.md: add Affected Nested Repositories section
- Update tasks.md command: guidance for generating branch-creation task
- Update tasks-template.md: Phase 1 example for nested repo branching
- Update specify.md: remove nested repo guidance, point to plan+tasks
- Rewrite tests: 13 tests covering discovery, depth config, setup-plan
  output, and verification that specify phase doesn't branch nested repos

Resolves the concern that at specify time the spec doesn't exist yet,
so we cannot know which repos are affected. The AI agent now makes this
determination during planning based on spec analysis.

Refs: github#2120

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 8, 2026 08:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

templates/commands/plan.md:88

  • Outline numbering is inconsistent: there are two items numbered 5. (Stop and report, then Check for extension hooks). This can confuse agents/instructions parsing; please renumber the second one to 6. (and ensure subsequent numbering remains consistent).
   - Phase 1: Generate data-model.md, contracts/, quickstart.md
   - Phase 1: Update agent context by running the agent script
   - Re-evaluate Constitution Check post-design

5. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, generated artifacts, and affected nested repos (if any).

5. **Check for extension hooks**: After reporting, check if `.specify/extensions.yml` exists in the project root.
   - If it exists, read it and look for entries under the `hooks.after_plan` key
   - If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 90 to 95
- You must only ever run this script once per feature
- The JSON is provided in the terminal as output - always refer to it to get the actual content you're looking for
- The JSON output will contain BRANCH_NAME and SPEC_FILE paths
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot")
- **Note**: Nested git repository branching is handled during the `/speckit.plan` and `/speckit.tasks` phases, not here. The plan identifies affected repos and tasks generates a setup task for branch creation.

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The PR description/title says create-new-feature now creates matching branches in nested repos and reports NESTED_REPOS, but this note explicitly states nested repo branching is not handled in specify (create-new-feature) and is deferred to plan/tasks. Please reconcile the implementation + docs/tests with the PR description (either implement branching in create-new-feature + add NESTED_REPOS there, or update the PR description/title to match the new workflow).

Copilot uses AI. Check for mistakes.
Comment on lines +248 to +258
class TestCreateFeatureNoNestedBranching:
def test_no_nested_repos_in_json(self, git_repo_with_nested: Path):
"""create-new-feature JSON output should NOT contain NESTED_REPOS."""
result = run_create_feature(
git_repo_with_nested,
"--json", "--short-name", "my-feat", "Add a feature",
)
assert result.returncode == 0, result.stderr
data = json.loads(result.stdout.strip())
assert "NESTED_REPOS" not in data
assert "BRANCH_NAME" in data
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

These tests assert that create-new-feature.sh does not branch nested repos and that its JSON output omits NESTED_REPOS, which directly contradicts the PR description stating create-new-feature now coordinates branching across nested repos and reports NESTED_REPOS. Either the feature implementation is missing from create-new-feature, or the PR description/tests need to be updated to match the intended behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +271 to +299
_should_skip() {
local name="$1"
for skip in "${skip_dirs[@]}"; do
[ "$name" = "$skip" ] && return 0
done
return 1
}

_scan_dir() {
local dir="$1"
local current_depth="$2"
for child in "$dir"/*/; do
[ -d "$child" ] || continue
child="${child%/}"
local child_name
child_name="$(basename "$child")"
_should_skip "$child_name" && continue

if [ -e "$child/.git" ]; then
if git -C "$child" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$child"
fi
elif [ "$current_depth" -lt "$max_depth" ]; then
_scan_dir "$child" $((current_depth + 1))
fi
done
}

_scan_dir "$repo_root" 1
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

find_nested_git_repos defines helper functions (_should_skip, _scan_dir) inside the function body. In bash this still creates global function names, which can unintentionally pollute/override other helpers in common.sh and makes the namespace harder to maintain. Consider inlining this logic, using more uniquely prefixed helper names (e.g. __find_nested_git_repos_*), or running the scan in a subshell to avoid exporting generic helper names globally.

Suggested change
_should_skip() {
local name="$1"
for skip in "${skip_dirs[@]}"; do
[ "$name" = "$skip" ] && return 0
done
return 1
}
_scan_dir() {
local dir="$1"
local current_depth="$2"
for child in "$dir"/*/; do
[ -d "$child" ] || continue
child="${child%/}"
local child_name
child_name="$(basename "$child")"
_should_skip "$child_name" && continue
if [ -e "$child/.git" ]; then
if git -C "$child" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$child"
fi
elif [ "$current_depth" -lt "$max_depth" ]; then
_scan_dir "$child" $((current_depth + 1))
fi
done
}
_scan_dir "$repo_root" 1
(
_should_skip() {
local name="$1"
local skip
for skip in "${skip_dirs[@]}"; do
[ "$name" = "$skip" ] && return 0
done
return 1
}
_scan_dir() {
local dir="$1"
local current_depth="$2"
local child
local child_name
for child in "$dir"/*/; do
[ -d "$child" ] || continue
child="${child%/}"
child_name="$(basename "$child")"
_should_skip "$child_name" && continue
if [ -e "$child/.git" ]; then
if git -C "$child" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "$child"
fi
elif [ "$current_depth" -lt "$max_depth" ]; then
_scan_dir "$child" $((current_depth + 1))
fi
done
}
_scan_dir "$repo_root" 1
)

Copilot uses AI. Check for mistakes.
Comment on lines 35 to 37
# Reset sentinel if --scan-depth was passed without a value
[ "$SCAN_DEPTH" = "__NEXT__" ] && SCAN_DEPTH=""

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

--scan-depth is accepted as an arbitrary string and is passed through to find_nested_git_repos, which later uses -lt numeric comparisons. If the value is missing or non-numeric (e.g. --scan-depth --json), this can produce integer expression expected errors and unexpected scan behavior. Consider validating that SCAN_DEPTH is a positive integer when parsing args and exiting with a clear error if it isn't.

Suggested change
# Reset sentinel if --scan-depth was passed without a value
[ "$SCAN_DEPTH" = "__NEXT__" ] && SCAN_DEPTH=""
# Validate --scan-depth argument if provided
if [ "$SCAN_DEPTH" = "__NEXT__" ]; then
echo "ERROR: --scan-depth requires a positive integer value" >&2
exit 1
fi
if [ -n "$SCAN_DEPTH" ]; then
case "$SCAN_DEPTH" in
''|*[!0-9]*|0)
echo "ERROR: --scan-depth must be a positive integer" >&2
exit 1
;;
esac
fi

Copilot uses AI. Check for mistakes.
Comment on lines 65 to 66
**Nested repo scan depth**: Check `.specify/init-options.json` for `nested_repo_scan_depth`. If present, pass `--scan-depth N` (Bash) or `-ScanDepth N` (PowerShell) to the script.

Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

This template instructs the agent to read .specify/init-options.json for nested_repo_scan_depth, but that key doesn't appear to be written/used anywhere else in the repo. Either add support for persisting this option (and document it), or remove/adjust this instruction so agents don’t look for a config value that will never exist.

Suggested change
**Nested repo scan depth**: Check `.specify/init-options.json` for `nested_repo_scan_depth`. If present, pass `--scan-depth N` (Bash) or `-ScanDepth N` (PowerShell) to the script.

Copilot uses AI. Check for mistakes.
3. **Execute task generation workflow**:
- Load plan.md and extract tech stack, libraries, project structure
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
- If plan.md contains an "Affected Nested Repositories" section: extract the repo paths and reasons. Generate a setup task (in Phase 1) to create the feature branch in each affected nested repo using `git -C <repo_path> checkout -b <BRANCH_NAME>`. The branch name comes from the current feature branch.
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The generated setup task command should quote <repo_path> (and ideally <BRANCH_NAME>) to handle paths with spaces and avoid shell parsing issues. Consider specifying git -C "<repo_path>" checkout -b "<BRANCH_NAME>" (or equivalent) in this instruction text.

Suggested change
- If plan.md contains an "Affected Nested Repositories" section: extract the repo paths and reasons. Generate a setup task (in Phase 1) to create the feature branch in each affected nested repo using `git -C <repo_path> checkout -b <BRANCH_NAME>`. The branch name comes from the current feature branch.
- If plan.md contains an "Affected Nested Repositories" section: extract the repo paths and reasons. Generate a setup task (in Phase 1) to create the feature branch in each affected nested repo using `git -C "<repo_path>" checkout -b "<BRANCH_NAME>"`. The branch name comes from the current feature branch.

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +210
# Discover nested independent git repositories under RepoRoot.
# Searches up to 2 directory levels deep for subdirectories containing .git
# (directory or file, covering worktrees/submodules). Excludes the root repo
# itself and common non-project directories.
# Returns an array of absolute paths.
function Find-NestedGitRepos {
param(
[string]$RepoRoot = (Get-RepoRoot),
[int]$MaxDepth = 2
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The comment above Find-NestedGitRepos says it "Searches up to 2 directory levels deep", but the implementation accepts a configurable -MaxDepth parameter. Please update the comment to reflect that scan depth is configurable (default 2) so the documentation matches behavior.

Copilot uses AI. Check for mistakes.
- Wrap find_nested_git_repos helpers in subshell to prevent bash
  global namespace pollution (_should_skip, _scan_dir)
- Validate --scan-depth is a positive integer in setup-plan.sh
- Remove non-existent nested_repo_scan_depth config reference from
  plan.md template
- Fix duplicate step numbering (two items numbered 5) in plan.md
- Quote repo paths in tasks.md git command template
- Update Find-NestedGitRepos comment to reflect configurable depth

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sakitA sakitA changed the title feat: Support coordinated feature branching across nested independent git repos feat: Plan-aware nested repo branching for multi-module workspaces Apr 8, 2026
@mnriem
Copy link
Copy Markdown
Collaborator

mnriem commented Apr 8, 2026

Please deliver this as a community preset. See https://github.com/github/spec-kit/tree/main/presets

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support coordinated feature branching across nested independent git repositories

3 participants