feat(installer): auto-detect git repo and pre-fill bootstrap prompts

During installation, the Claude Code installer now:

1. Detects the git remote URL (supports GitHub.com and Enterprise)
2. Generates a pre-filled bootstrap prompt at _bmad/claude-desktop-bootstrap.md
3. Replaces YOUR-ORG/YOUR-REPO placeholders in all installed docs

This enables seamless Claude Desktop usage - users just copy the
generated bootstrap prompt which has their repo details already filled in.

Supports all git URL formats:
- SSH: git@github.com:owner/repo.git
- SSH Enterprise: git@ghe.company.com:owner/repo.git
- HTTPS: https://github.com/owner/repo.git
- HTTP Enterprise: http://ghe.company.com/owner/repo
This commit is contained in:
Jonah Schulte 2026-01-08 20:51:55 -05:00
parent 11763ba343
commit 0efbea9136
2 changed files with 277 additions and 0 deletions

View File

@ -13,6 +13,7 @@ const {
resolveSubagentFiles,
} = require('./shared/module-injections');
const { getAgentsFromBmad, getAgentsFromDir } = require('./shared/bmad-artifacts');
const { detectGitRepo, generateBootstrapPrompt } = require('../utils/git-repo-detector');
/**
* Claude Code IDE setup handler
@ -154,6 +155,9 @@ class ClaudeCodeSetup extends BaseIdeSetup {
// Install BMAD Guide skill to user's Claude skills directory
await this.installBmadGuideSkill();
// Detect git repo and generate bootstrap prompt + replace placeholders
await this.setupGitHubIntegration(projectDir, bmadDir);
// Generate workflow commands from manifest (if it exists)
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
@ -230,6 +234,120 @@ class ClaudeCodeSetup extends BaseIdeSetup {
}
}
/**
* Detect git repo and set up GitHub integration
* - Generates a pre-filled bootstrap prompt
* - Replaces YOUR-ORG/YOUR-REPO placeholders in documentation
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
*/
async setupGitHubIntegration(projectDir, bmadDir) {
try {
const repoInfo = detectGitRepo(projectDir);
if (!repoInfo) {
console.log(chalk.dim(' Skipping GitHub integration (not a git repo or no remote configured)'));
return;
}
console.log(chalk.cyan(` Detected GitHub repo: ${repoInfo.owner}/${repoInfo.repo}`));
if (repoInfo.isEnterprise) {
console.log(chalk.dim(` Enterprise host: ${repoInfo.host}`));
}
// Generate bootstrap prompt file
const bootstrapContent = generateBootstrapPrompt(repoInfo);
const bootstrapPath = path.join(bmadDir, 'claude-desktop-bootstrap.md');
await fs.writeFile(bootstrapPath, bootstrapContent, 'utf8');
console.log(chalk.green(' ✓ Generated Claude Desktop bootstrap prompt'));
console.log(chalk.dim(` Location: ${path.relative(projectDir, bootstrapPath)}`));
// Replace placeholders in documentation files
await this.replaceRepoPlaceholders(bmadDir, repoInfo);
} catch (error) {
console.log(chalk.yellow(` ⚠ Warning: GitHub integration setup failed: ${error.message}`));
}
}
/**
* Replace YOUR-ORG/YOUR-REPO placeholders in documentation files
* @param {string} bmadDir - BMAD installation directory
* @param {Object} repoInfo - Repository info from detectGitRepo
*/
async replaceRepoPlaceholders(bmadDir, repoInfo) {
// Patterns to replace
const replacements = [
{ pattern: /YOUR-ORG\/YOUR-REPO/g, replacement: `${repoInfo.owner}/${repoInfo.repo}` },
{ pattern: /github\.com\/YOUR-ORG\/YOUR-REPO/g, replacement: `${repoInfo.host}/${repoInfo.owner}/${repoInfo.repo}` },
{ pattern: /YOUR-ORG/g, replacement: repoInfo.owner },
{ pattern: /YOUR-REPO/g, replacement: repoInfo.repo },
];
// Find all markdown and yaml files in bmad directory
const files = await this.findFilesRecursive(bmadDir, ['.md', '.yaml', '.yml']);
let replacedCount = 0;
for (const filePath of files) {
try {
let content = await fs.readFile(filePath, 'utf8');
let modified = false;
for (const { pattern, replacement } of replacements) {
if (pattern.test(content)) {
content = content.replace(pattern, replacement);
modified = true;
}
}
if (modified) {
await fs.writeFile(filePath, content, 'utf8');
replacedCount++;
}
} catch {
// Skip files that can't be read/written
}
}
if (replacedCount > 0) {
console.log(chalk.dim(` Updated ${replacedCount} files with repo details`));
}
}
/**
* Recursively find files with specific extensions
* @param {string} dir - Directory to search
* @param {string[]} extensions - File extensions to match
* @returns {string[]} Array of file paths
*/
async findFilesRecursive(dir, extensions) {
const files = [];
try {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip node_modules and hidden directories
if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
const subFiles = await this.findFilesRecursive(fullPath, extensions);
files.push(...subFiles);
}
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (extensions.includes(ext)) {
files.push(fullPath);
}
}
}
} catch {
// Skip directories that can't be read
}
return files;
}
/**
* Read and process file content
*/

View File

@ -0,0 +1,159 @@
/**
* Git Repository Detector
* Detects and parses git remote URLs to extract repository information
*/
const { execSync } = require('node:child_process');
const path = require('node:path');
/**
* Parse a git remote URL into its components
* Handles SSH, HTTPS, and GitHub Enterprise formats
*
* @param {string} remoteUrl - The git remote URL
* @returns {Object|null} Parsed repo info or null if unparseable
*/
function parseGitRemoteUrl(remoteUrl) {
if (!remoteUrl) return null;
// Clean up the URL
const url = remoteUrl.trim();
// SSH format: git@github.com:owner/repo.git
const sshMatch = url.match(/^git@([^:]+):([^/]+)\/(.+?)(?:\.git)?$/);
if (sshMatch) {
return {
host: sshMatch[1],
owner: sshMatch[2],
repo: sshMatch[3],
fullUrl: `https://${sshMatch[1]}/${sshMatch[2]}/${sshMatch[3]}`,
isEnterprise: sshMatch[1] !== 'github.com',
};
}
// HTTPS format: https://github.com/owner/repo.git
// Also handles: http://ghe.company.com/owner/repo
const httpsMatch = url.match(/^https?:\/\/([^/]+)\/([^/]+)\/(.+?)(?:\.git)?$/);
if (httpsMatch) {
return {
host: httpsMatch[1],
owner: httpsMatch[2],
repo: httpsMatch[3],
fullUrl: `https://${httpsMatch[1]}/${httpsMatch[2]}/${httpsMatch[3]}`,
isEnterprise: httpsMatch[1] !== 'github.com',
};
}
return null;
}
/**
* Detect the git remote URL for a project directory
*
* @param {string} projectDir - The project directory path
* @returns {Object|null} Parsed repo info or null if not a git repo
*/
function detectGitRepo(projectDir) {
try {
const remoteUrl = execSync('git config --get remote.origin.url', {
cwd: projectDir,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
});
return parseGitRemoteUrl(remoteUrl);
} catch {
// Not a git repo or no remote configured
return null;
}
}
/**
* Generate the bootstrap prompt content with repo details filled in
*
* @param {Object} repoInfo - Repository info from detectGitRepo
* @param {string} agentPath - Path to the agent file in the repo
* @returns {string} The bootstrap prompt content
*/
function generateBootstrapPrompt(repoInfo, agentPath = 'src/modules/bmm/agents/po.agent.yaml') {
const repoRef = repoInfo.isEnterprise
? `${repoInfo.host}/${repoInfo.owner}/${repoInfo.repo}`
: `${repoInfo.owner}/${repoInfo.repo}`;
return `# BMAD Product Owner - Claude Desktop Bootstrap
This prompt is pre-configured for your repository.
---
## Quick Start
Copy and paste this into Claude Desktop:
\`\`\`
Load the Product Owner agent from ${repoInfo.fullUrl}
(path: ${agentPath}) and enter PO mode.
Show me what needs my attention.
\`\`\`
---
## Full Version
\`\`\`
Fetch and embody the BMAD Product Owner agent.
1. Read the agent definition from GitHub:
- Host: ${repoInfo.host}
- Repository: ${repoInfo.owner}/${repoInfo.repo}
- Path: ${agentPath}
2. After reading, fully embody this agent:
- Adopt the persona (name, role, communication style)
- Internalize all principles
- Make the menu commands available
3. Introduce yourself and show the available commands.
4. Then check: what PRDs or stories need my attention?
Use GitHub MCP tools (mcp__github__*) for all GitHub operations.
\`\`\`
---
## For Stakeholders
\`\`\`
I'm a stakeholder who needs to review PRDs and give feedback.
Load the Product Owner agent from ${repoInfo.fullUrl}
(path: ${agentPath})
Then show me:
1. What PRDs need my feedback
2. What PRDs need my sign-off
I'll mainly use: MT (my tasks), SF (submit feedback), SO (sign off)
\`\`\`
---
## Repository Details
| Field | Value |
|-------|-------|
| Host | ${repoInfo.host} |
| Owner | ${repoInfo.owner} |
| Repo | ${repoInfo.repo} |
| Enterprise | ${repoInfo.isEnterprise ? 'Yes' : 'No'} |
Generated during BMAD installation.
`;
}
module.exports = {
parseGitRemoteUrl,
detectGitRepo,
generateBootstrapPrompt,
};