Skip to content
This repository was archived by the owner on Apr 5, 2026. It is now read-only.

Commit ac5d3a0

Browse files
HerbHallclaude
andauthored
feat: new-project.ps1 steps 3-4 — CLAUDE.md generation and workspace open (issue #20) (#46)
Step 3 generates a project CLAUDE.md via claude --print (with Y/n/edit confirmation loop) or falls back to template substitution when Claude is not authenticated. Creates Phase 0 GitHub issue from concept brief. Step 4 opens the VS Code workspace and shows final summary with next steps. Co-authored-by: Claude <noreply@anthropic.com>
1 parent a974652 commit ac5d3a0

2 files changed

Lines changed: 348 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
- `profiles/iot-embedded.md`: ESP32/ESPHome profile (uv-managed tools, OTA/serial flashing, sensor patterns)
4141
- `setup/stack.ps1`: Kit 2 profile selection and installer (-List, -ShowProfile, -Install, -Force flags)
4242
- `setup/new-project.ps1` steps 1-2: concept collection (interactive + file), project scaffolding (git, GitHub, directories, labels, workspace)
43+
- `setup/new-project.ps1` steps 3-4: Claude-generated CLAUDE.md (with template fallback), Phase 0 issue creation, workspace open
4344

4445
### Changed
4546

setup/new-project.ps1

Lines changed: 347 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
#Requires -Version 7.0
2-
# setup/new-project.ps1 -- Kit 3: New project scaffolder (Steps 1-2)
2+
# setup/new-project.ps1 -- Kit 3: New project scaffolder (Steps 1-4)
33
#
4-
# Collects project concept, creates directory structure, and initializes
5-
# the git repo and GitHub remote.
6-
#
7-
# Steps 3-4 (CLAUDE.md generation and workspace open) will be added in issue #20.
4+
# Collects project concept, creates directory structure, initializes
5+
# the git repo and GitHub remote, generates CLAUDE.md, and opens VS Code.
86
#
97
# Usage:
108
# .\setup\new-project.ps1 # Fully interactive
@@ -851,32 +849,359 @@ logger:
851849
}
852850

853851
# ---------------------------------------------------------------------------
854-
# Main entry point
852+
# STEP 3: CLAUDE.md Generation
855853
# ---------------------------------------------------------------------------
856854

857-
Write-Section 'Kit 3: New Project'
858-
Write-Host ' Collects project concept and creates directory structure.'
859-
Write-Host ''
855+
function Invoke-ClaudeMdGeneration {
856+
<#
857+
.SYNOPSIS
858+
Generates a project CLAUDE.md using Claude Code or a template fallback.
859+
.PARAMETER Data
860+
Hashtable returned by Invoke-ConceptCollection.
861+
.PARAMETER ScaffoldResult
862+
Hashtable returned by Invoke-Scaffolding.
863+
#>
864+
[CmdletBinding()]
865+
param(
866+
[Parameter(Mandatory)]
867+
[hashtable]$Data,
860868

861-
$conceptData = Invoke-ConceptCollection
862-
$scaffoldResult = Invoke-Scaffolding -Data $conceptData
869+
[Parameter(Mandatory)]
870+
[hashtable]$ScaffoldResult
871+
)
872+
873+
Write-Section 'Step 3: CLAUDE.md Generation'
874+
875+
$projectName = $Data.Name
876+
$githubUser = $Data.GitHubUser
877+
$targetDir = $ScaffoldResult.TargetDir
878+
$concept = $Data.Concept
879+
$profiles = @($Data.Profiles)
880+
$primaryProfile = if ($profiles.Count -gt 0) { $profiles[0] } else { '' }
881+
882+
$claudeMdPath = Join-Path $targetDir 'CLAUDE.md'
883+
$claudeMdWritten = $false
884+
$issueCreated = $false
885+
$issueUrl = ''
886+
887+
# ------------------------------------------------------------------
888+
# 3.1 Check Claude auth and choose generation path
889+
# ------------------------------------------------------------------
890+
891+
$authResult = Test-ClaudeAuth
892+
$useClaudeAi = $authResult.Met
893+
894+
if ($useClaudeAi) {
895+
Write-Step 'Claude Code authenticated -- generating CLAUDE.md with AI'
896+
897+
# Build profile body text for the prompt
898+
$profileBody = ''
899+
if ($primaryProfile -ne '') {
900+
$profileObj = Get-Profile -Name $primaryProfile
901+
if ($null -ne $profileObj -and $profileObj.Body) {
902+
$profileBody = $profileObj.Body
903+
}
904+
}
905+
906+
# Read the global CLAUDE.md as format reference
907+
$globalClaudeMd = ''
908+
$globalClaudeMdPath = Join-Path $script:RepoRoot 'claude' 'CLAUDE.md'
909+
if (Test-Path $globalClaudeMdPath) {
910+
$globalClaudeMd = Get-Content $globalClaudeMdPath -Raw
911+
}
912+
913+
# Build features and non-goals as text
914+
$featuresText = if ($concept.Features.Count -gt 0) {
915+
($concept.Features | ForEach-Object { "- $_" }) -join "`n"
916+
} else {
917+
'(not specified)'
918+
}
919+
$nonGoalsText = if ($concept.NonGoals.Count -gt 0) {
920+
($concept.NonGoals | ForEach-Object { "- $_" }) -join "`n"
921+
} else {
922+
'(not specified)'
923+
}
924+
925+
$prompt = @"
926+
Generate a project CLAUDE.md for a new software project. Include: project context, goals and non-goals, tech stack and conventions, build commands, key constraints, and suggested first steps.
927+
928+
PROJECT CONCEPT BRIEF:
929+
Name: $projectName
930+
Description: $($concept.Description)
931+
Problem: $($concept.Problem)
932+
Features:
933+
$featuresText
934+
Non-goals:
935+
$nonGoalsText
936+
Profile: $primaryProfile
937+
GitHub: https://github.com/$githubUser/$projectName
938+
939+
PROFILE DETAILS:
940+
$profileBody
941+
942+
FORMAT REFERENCE (follow the structure and style of this global CLAUDE.md):
943+
$globalClaudeMd
944+
945+
Generate only the CLAUDE.md content. Do not include any preamble or explanation.
946+
"@
947+
948+
$generatedContent = $null
949+
$accepted = $false
950+
951+
while (-not $accepted) {
952+
Write-Step 'Calling Claude Code to generate CLAUDE.md...'
953+
try {
954+
$rawOutput = & claude --print -p $prompt 2>&1 | Out-String
955+
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($rawOutput)) {
956+
Write-Warn "Claude returned exit code $LASTEXITCODE or empty output -- falling back to template"
957+
$useClaudeAi = $false
958+
break
959+
}
960+
$generatedContent = $rawOutput.Trim()
961+
} catch {
962+
Write-Warn "Claude Code call failed: $_ -- falling back to template"
963+
$useClaudeAi = $false
964+
break
965+
}
966+
967+
# Display result
968+
Write-Host ''
969+
Write-Host "${script:Bold}--- Generated CLAUDE.md ---${script:Reset}"
970+
Write-Host $generatedContent
971+
Write-Host "${script:Bold}--- End of CLAUDE.md ---${script:Reset}"
972+
Write-Host ''
973+
974+
$choice = Read-Host -Prompt ' Accept this CLAUDE.md? [Y/n/edit]'
975+
$choice = $choice.Trim().ToLower()
976+
977+
if ($choice -eq '' -or $choice -eq 'y') {
978+
$accepted = $true
979+
} elseif ($choice -eq 'n') {
980+
Write-Step 'Regenerating...'
981+
# Loop again
982+
} elseif ($choice -eq 'edit') {
983+
$tempFile = Join-Path ([System.IO.Path]::GetTempPath()) "claude-md-$projectName.md"
984+
Set-Content -Path $tempFile -Value $generatedContent -Encoding utf8NoBOM
985+
Write-Step "Opening in VS Code: $tempFile"
986+
& code --wait $tempFile
987+
if (Test-Path $tempFile) {
988+
$generatedContent = Get-Content $tempFile -Raw
989+
}
990+
$accepted = $true
991+
} else {
992+
Write-Warn "Unrecognized choice '$choice'. Press Enter or type y to accept, n to regenerate, edit to open in VS Code."
993+
}
994+
}
995+
996+
if ($useClaudeAi -and $accepted -and $null -ne $generatedContent) {
997+
try {
998+
Set-Content -Path $claudeMdPath -Value $generatedContent -Encoding utf8NoBOM
999+
Write-OK 'CLAUDE.md written from AI generation'
1000+
$claudeMdWritten = $true
1001+
} catch {
1002+
Write-Warn "Could not write CLAUDE.md: $_"
1003+
}
1004+
}
1005+
}
1006+
1007+
# ------------------------------------------------------------------
1008+
# 3.2 Fallback: template substitution
1009+
# ------------------------------------------------------------------
1010+
1011+
if (-not $claudeMdWritten) {
1012+
Write-Warn 'Claude Code not authenticated -- using template fallback'
1013+
1014+
$templatePath = Join-Path $script:RepoRoot 'project-templates' 'claude-md-template.md'
1015+
if (-not (Test-Path $templatePath)) {
1016+
Write-Warn "Template not found: $templatePath -- skipping CLAUDE.md creation"
1017+
} else {
1018+
# Map profile to file extension
1019+
$profileExt = switch ($primaryProfile) {
1020+
'go-cli' { 'go' }
1021+
'go-web' { 'go' }
1022+
'iot-embedded' { 'yaml' }
1023+
default { 'ts' }
1024+
}
1025+
1026+
$machine = if ($env:COMPUTERNAME) { $env:COMPUTERNAME } else { hostname }
1027+
1028+
$templateContent = Get-Content $templatePath -Raw
1029+
$templateContent = $templateContent -replace '\{\{PROJECT_NAME\}\}', $projectName
1030+
$templateContent = $templateContent -replace '\{\{PROJECT_DESCRIPTION\}\}', $concept.Description
1031+
$templateContent = $templateContent -replace '\{\{PROFILE\}\}', $primaryProfile
1032+
$templateContent = $templateContent -replace '\{\{DEVSPACE\}\}', $Data.DevSpace
1033+
$templateContent = $templateContent -replace '\{\{AUTHOR\}\}', $githubUser
1034+
$templateContent = $templateContent -replace '\{\{MACHINE\}\}', $machine
1035+
$templateContent = $templateContent -replace '\{\{PROFILE_EXT\}\}', $profileExt
1036+
1037+
try {
1038+
Set-Content -Path $claudeMdPath -Value $templateContent -Encoding utf8NoBOM
1039+
Write-OK 'CLAUDE.md written from template'
1040+
$claudeMdWritten = $true
1041+
} catch {
1042+
Write-Warn "Could not write CLAUDE.md: $_"
1043+
}
1044+
}
1045+
}
1046+
1047+
# ------------------------------------------------------------------
1048+
# 3.3 Create Phase 0 GitHub issue
1049+
# ------------------------------------------------------------------
1050+
1051+
if ($ScaffoldResult.GitHubCreated) {
1052+
Write-Step 'Creating Phase 0 GitHub issue...'
1053+
1054+
$featuresSection = if ($concept.Features.Count -gt 0) {
1055+
($concept.Features | ForEach-Object { "- $_" }) -join "`n"
1056+
} else {
1057+
'- (not specified)'
1058+
}
1059+
$nonGoalsSection = if ($concept.NonGoals.Count -gt 0) {
1060+
($concept.NonGoals | ForEach-Object { "- $_" }) -join "`n"
1061+
} else {
1062+
'- (not specified)'
1063+
}
1064+
1065+
$issueBody = @"
1066+
## Concept
1067+
1068+
$($concept.Description)
1069+
1070+
## Problem statement
1071+
1072+
$($concept.Problem)
1073+
1074+
## Core features
1075+
1076+
$featuresSection
1077+
1078+
## Non-goals
1079+
1080+
$nonGoalsSection
1081+
1082+
## Acceptance criteria
1083+
1084+
- [ ] Concept validated with at least one potential user
1085+
- [ ] Tech stack confirmed appropriate for the problem
1086+
- [ ] Non-goals agreed upon by stakeholders
1087+
- [ ] CLAUDE.md reviewed and updated with any corrections
1088+
1089+
## Next steps
1090+
1091+
1. Review and refine this concept brief
1092+
2. Set up the development environment (`.\setup\stack.ps1`)
1093+
3. Create architecture doc in `docs/ARCHITECTURE.md`
1094+
4. Break the concept into implementable issues
1095+
"@
1096+
1097+
try {
1098+
$ghIssueOutput = & gh issue create `
1099+
-R "$githubUser/$projectName" `
1100+
--title "Phase 0 -- Concept validation: $projectName" `
1101+
--body $issueBody `
1102+
--label 'chore' `
1103+
--label 'phase-1' 2>&1 | Out-String
1104+
$ghIssueExit = $LASTEXITCODE
1105+
1106+
if ($ghIssueExit -eq 0) {
1107+
$issueUrl = $ghIssueOutput.Trim()
1108+
Write-OK "Phase 0 issue created: $issueUrl"
1109+
$issueCreated = $true
1110+
} else {
1111+
Write-Warn "Could not create GitHub issue (exit $ghIssueExit): $ghIssueOutput"
1112+
}
1113+
} catch {
1114+
Write-Warn "gh issue create threw exception: $_"
1115+
}
1116+
}
1117+
1118+
return @{
1119+
ClaudeMdWritten = $claudeMdWritten
1120+
IssueCreated = $issueCreated
1121+
IssueUrl = $issueUrl
1122+
}
1123+
}
8631124

8641125
# ---------------------------------------------------------------------------
865-
# Final summary
1126+
# STEP 4: Workspace Open
8661127
# ---------------------------------------------------------------------------
8671128

868-
Write-Section 'Done'
869-
Write-OK "Project scaffolded: $($scaffoldResult.TargetDir)"
1129+
function Invoke-WorkspaceOpen {
1130+
<#
1131+
.SYNOPSIS
1132+
Displays the project summary and opens VS Code.
1133+
.PARAMETER Data
1134+
Hashtable returned by Invoke-ConceptCollection.
1135+
.PARAMETER ScaffoldResult
1136+
Hashtable returned by Invoke-Scaffolding.
1137+
.PARAMETER ClaudeMdResult
1138+
Hashtable returned by Invoke-ClaudeMdGeneration.
1139+
#>
1140+
[CmdletBinding()]
1141+
param(
1142+
[Parameter(Mandatory)]
1143+
[hashtable]$Data,
1144+
1145+
[Parameter(Mandatory)]
1146+
[hashtable]$ScaffoldResult,
1147+
1148+
[Parameter(Mandatory)]
1149+
[hashtable]$ClaudeMdResult
1150+
)
1151+
1152+
$projectName = $Data.Name
1153+
$targetDir = $ScaffoldResult.TargetDir
1154+
1155+
Write-Section "Project ready: $projectName"
1156+
1157+
Write-Host " Location: $targetDir"
1158+
if ($ScaffoldResult.GitHubCreated) {
1159+
Write-Host " GitHub: $($ScaffoldResult.RepoUrl)"
1160+
}
1161+
if ($ClaudeMdResult.IssueCreated) {
1162+
Write-Host " Issue #1: $($ClaudeMdResult.IssueUrl)"
1163+
}
1164+
Write-Host ''
1165+
1166+
# Open VS Code workspace
1167+
$workspacePath = Join-Path $targetDir "$projectName.code-workspace"
1168+
if (Test-Path $workspacePath) {
1169+
Write-Step "Opening VS Code: $workspacePath"
1170+
try {
1171+
& code $workspacePath
1172+
} catch {
1173+
Write-Warn "Could not open VS Code: $_"
1174+
Write-Host " Manual: code `"$workspacePath`""
1175+
}
1176+
} else {
1177+
Write-Step "Opening VS Code: $targetDir"
1178+
try {
1179+
& code $targetDir
1180+
} catch {
1181+
Write-Warn "Could not open VS Code: $_"
1182+
Write-Host " Manual: code `"$targetDir`""
1183+
}
1184+
}
8701185

871-
if ($scaffoldResult.GitHubCreated) {
872-
Write-OK "GitHub repo: $($scaffoldResult.RepoUrl)"
1186+
Write-Host ''
1187+
Write-Host ' Next steps:'
1188+
Write-Host " 1. Review CLAUDE.md in $targetDir"
1189+
if ($ClaudeMdResult.IssueCreated) {
1190+
Write-Host " 2. Open GitHub issue #1 to review the concept: $($ClaudeMdResult.IssueUrl)"
1191+
}
1192+
Write-Host " 3. Run \`claude\` in the project directory to begin planning"
1193+
Write-Host ''
8731194
}
8741195

875-
Write-Host ''
876-
Write-Host ' Next steps:'
877-
Write-Host " 1. cd `"$($scaffoldResult.TargetDir)`""
878-
Write-Host ' 2. Run .\setup\stack.ps1 to install toolchain for your chosen profiles'
879-
Write-Host ' 3. Steps 3-4 (CLAUDE.md generation and workspace open) coming in issue #20'
1196+
# ---------------------------------------------------------------------------
1197+
# Main entry point
1198+
# ---------------------------------------------------------------------------
1199+
1200+
Write-Section 'Kit 3: New Project'
1201+
Write-Host ' Collects project concept and creates directory structure.'
8801202
Write-Host ''
8811203

882-
# Steps 3-4 (CLAUDE.md generation and workspace open) will be added in issue #20
1204+
$conceptData = Invoke-ConceptCollection
1205+
$scaffoldResult = Invoke-Scaffolding -Data $conceptData
1206+
$claudeMdResult = Invoke-ClaudeMdGeneration -Data $conceptData -ScaffoldResult $scaffoldResult
1207+
Invoke-WorkspaceOpen -Data $conceptData -ScaffoldResult $scaffoldResult -ClaudeMdResult $claudeMdResult

0 commit comments

Comments
 (0)