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
66 changes: 66 additions & 0 deletions Pull-SDLC.ai.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3575,3 +3575,69 @@ Describe 'Invoke-PullSDLC anchor-only sync (issue #235)' {
$state.lastSyncCommit | Should -Be $fx.UpstreamHead -Because 'a real sync must record the new upstream head'
}
}

Describe 'Invoke-PullSDLC scaffolds into the real checkout (issue #237)' {
BeforeAll {
$script:fixtureRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("sdlc-scaffold-root-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $script:fixtureRoot -Force | Out-Null
}
AfterAll {
if ($script:fixtureRoot -and (Test-Path $script:fixtureRoot)) {
Remove-Item -LiteralPath $script:fixtureRoot -Recurse -Force -ErrorAction SilentlyContinue
}
}

# Builds an upstream that gains a consumer-owned same-name scaffold file
# (docs/README.md) plus a real managed change, and a consumer sitting on
# the protected branch so the auto-worktree path is taken.
function global:New-ScaffoldFixture {
param([Parameter(Mandatory)][string]$Base)
$root = Join-Path $Base ([guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $root -Force | Out-Null
$fx = New-DiffReplayFixture -Root $root `
-Seed {
New-Item -ItemType Directory -Path .github/agents -Force | Out-Null
'baseline-claude' | Out-File -Encoding utf8 CLAUDE.md -NoNewline
} `
-Tweak {
New-Item -ItemType Directory -Path docs -Force | Out-Null
'consumer first draft' | Out-File -Encoding utf8 docs/README.md -NoNewline
'updated-claude' | Out-File -Encoding utf8 CLAUDE.md -NoNewline
}
Push-Location $fx.Consumer
try {
git checkout -q main
git branch -q -D chore/sdlc-sync 2>&1 | Out-Null
$json = '{"remote":"sdlc.ai","ref":"main","lastSyncCommit":"' + $fx.AnchorSha + '","syncedAt":"2000-01-01T00:00:00Z"}'
[System.IO.File]::WriteAllText((Join-Path $fx.Consumer '.sdlc-ai-sync.json'), $json + "`n", (New-Object System.Text.UTF8Encoding $false))
git add -A | Out-Null
git commit -q -m 'seed sync anchor'
} finally { Pop-Location }
return $fx
}

It 'writes scaffolded consumer-owned files into the checkout, not the sync worktree' {
$fx = New-ScaffoldFixture -Base $script:fixtureRoot

$rc = Invoke-PullSDLC -RepoRoot $fx.Consumer -RemoteName 'sdlc.ai' -NoFetch -NoAutoPR
$rc | Should -Be 0

$inCheckout = Join-Path $fx.Consumer 'docs/README.md'
Test-Path -LiteralPath $inCheckout | Should -BeTrue -Because 'scaffolded files must reach the tree the user actually works in'
(Get-Content -LiteralPath $inCheckout -Raw).TrimEnd("`r", "`n") | Should -Be 'consumer first draft'
}

It 'does not add scaffolded consumer-owned files to the sync commit' {
$fx = New-ScaffoldFixture -Base $script:fixtureRoot

$rc = Invoke-PullSDLC -RepoRoot $fx.Consumer -RemoteName 'sdlc.ai' -NoFetch -NoAutoPR
$rc | Should -Be 0

Push-Location $fx.Consumer
try {
$tracked = @(git ls-tree -r --name-only chore/sdlc-sync)
} finally { Pop-Location }
$tracked | Should -Not -Contain 'docs/README.md' -Because 'consumer-owned files stay out of the upstream sync commit'
}
}

17 changes: 14 additions & 3 deletions Pull-SDLC.ai.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,13 @@ function Invoke-PullSDLC {
[string]$RemoteName = 'sdlc.ai',
[string]$RemoteUrl = 'https://github.com/IntelliTect-Samples/IntelliSDLC.ai.git',
[string]$RepoRoot,
# Internal plumbing (not a CLI switch): where consumer-owned template
# scaffolding is written. Defaults to $RepoRoot. The auto-worktree
# flow re-invokes with $RepoRoot pointed at the throwaway sync
# worktree, so it passes the user's real checkout here -- otherwise
# scaffolded files land in scratch space and are silently discarded
# (issue #237).
[string]$ScaffoldRoot,
[switch]$Force,
[switch]$Bootstrap,
[switch]$NoPrompt,
Expand Down Expand Up @@ -2355,6 +2362,7 @@ function Invoke-PullSDLC {
NoPrompt = [bool]$NoPrompt
NoFetch = [bool]$NoFetch
NoAutoWorktree = $true
ScaffoldRoot = $RepoRoot
}
$rc = Invoke-AutoWorktreeSync -RepoRoot $RepoRoot -ProtectedBranch $Branch -NoAutoPR:$NoAutoPR -SyncArgs $syncArgs
if ($rc -eq 0) {
Expand Down Expand Up @@ -2553,23 +2561,26 @@ function Invoke-PullSDLC {
}
finally { Pop-Location }

# Scaffold consumer-owned files from templates (first sync only).
# Scaffold consumer-owned files from templates (first sync only). Templates
# are read from the synced tree ($RepoRoot) but written to the user's real
# checkout, which differs from $RepoRoot on the auto-worktree path.
$scaffoldTarget = if ($ScaffoldRoot) { $ScaffoldRoot } else { $RepoRoot }
$scaffolded = @()
# $isUpstreamRepo was computed earlier (scoped to $RepoRoot, not the shell's
# CWD) so tests and maintainer-run consumer fixtures are classified correctly.
if ($isUpstreamRepo) {
Write-Information "Detected upstream repo (origin -> IntelliSDLC.ai). Skipping template scaffolding."
}
else {
$scaffolded = @(Invoke-TemplateScaffold -SourceRoot $RepoRoot -TargetRoot $RepoRoot -ScaffoldMap $script:TemplateScaffoldMap -Ref $mergeRef)
$scaffolded = @(Invoke-TemplateScaffold -SourceRoot $RepoRoot -TargetRoot $scaffoldTarget -ScaffoldMap $script:TemplateScaffoldMap -Ref $mergeRef)
if ($scaffolded.Count -gt 0) {
Write-Information 'Scaffolded consumer-owned files from templates:'
foreach ($f in $scaffolded) { Write-Information " + $f" }
Write-Information 'Open each file and fill in the sections, then commit them to your repo.'
}
}

Write-NextStepsBanner -RepoRoot $RepoRoot -AnchorSource $anchorInfo.Source -ScaffoldedFiles $scaffolded
Write-NextStepsBanner -RepoRoot $scaffoldTarget -AnchorSource $anchorInfo.Source -ScaffoldedFiles $scaffolded

return 0
}
Expand Down
Loading