Merge branch 'main' into feat/add_iflow_cli_support

This commit is contained in:
Brian 2025-09-06 13:44:37 -05:00 committed by GitHub
commit ce38cc3f16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 98 additions and 96 deletions

3
.gitignore vendored
View File

@ -29,7 +29,8 @@ Thumbs.db
# IDE and editor configs
.windsurf/
.trae/
.bmad*/.cursor/
.bmad*/
.cursor/
# AI assistant files
CLAUDE.md

View File

@ -119,7 +119,7 @@ The BMAD-METHOD™ includes a powerful codebase flattener tool designed to prepa
### Features
- **AI-Optimized Output**: Generates clean XML format specifically designed for AI model consumption
- **Smart Filtering**: Automatically respects `.gitignore` patterns to exclude unnecessary files
- **Smart Filtering**: Automatically respects `.gitignore` patterns to exclude unnecessary files, plus optional project-level `.bmad-flattenignore` for additional exclusions
- **Binary File Detection**: Intelligently identifies and excludes binary files, focusing on source code
- **Progress Tracking**: Real-time progress indicators and comprehensive completion statistics
- **Flexible Output**: Customizable output file location and naming
@ -170,6 +170,18 @@ The generated XML file contains your project's text-based source files in a stru
- File discovery and ignoring
- Uses `git ls-files` when inside a git repository for speed and correctness; otherwise falls back to a glob-based scan.
- Applies your `.gitignore` plus a curated set of default ignore patterns (e.g., `node_modules`, build outputs, caches, logs, IDE folders, lockfiles, large media/binaries, `.env*`, and previously generated XML outputs).
- Supports an optional `.bmad-flattenignore` file at the project root for additional ignore patterns (gitignore-style). If present, its rules are applied after `.gitignore` and the defaults.
##### `.bmad-flattenignore` example
Create a `.bmad-flattenignore` file in the root of your project to exclude files that must remain in git but should not be included in the flattened XML:
```text
seeds/**
scripts/private/**
**/*.snap
```
- Binary handling
- Binary files are detected and excluded from the XML content. They are counted in the final summary but not embedded in the output.
- XML format and safety

View File

@ -21,7 +21,7 @@ To comprehensively validate a story draft before implementation begins, ensuring
### 1. Template Completeness Validation
- Load `bmad-core/templates/story-tmpl.md` and extract all section headings from the template
- Load `.bmad-core/templates/story-tmpl.yaml` and extract all section headings from the template
- **Missing sections check**: Compare story sections against template sections to verify all required sections are present
- **Placeholder validation**: Ensure no template placeholders remain unfilled (e.g., `{{EpicNum}}`, `{{role}}`, `_TBD_`)
- **Agent section verification**: Confirm all sections from template exist for future agent use

View File

@ -5,7 +5,13 @@
> Gemini Web's 1M+ token context window or Gemini CLI (when it's working) can analyze your ENTIRE codebase, or critical sections of it, all at once (obviously within reason):
>
> - Upload via GitHub URL or use gemini cli in the project folder
> - If working in the web: use `npx bmad-method flatten` to flatten your project into a single file, then upload that file to your web agent.
> - If working in the web: use `npx bmad-method flatten` to flatten your project into a single file, then upload that file to your web agent. To exclude additional files that must remain in git but shouldn't be sent to the AI, add a `.bmad-flattenignore` file at the project root (gitignore-style), e.g.:
>
> ```text
> seeds/**
> scripts/private/**
> **/*.snap
> ```
## What is Brownfield Development?

View File

@ -154,9 +154,11 @@ async function parseGitignore(gitignorePath) {
async function loadIgnore(rootDir, extraPatterns = []) {
const ig = ignore();
const gitignorePath = path.join(rootDir, '.gitignore');
const flattenIgnorePath = path.join(rootDir, '.bmad-flattenignore');
const patterns = [
...(await readIgnoreFile(gitignorePath)),
...DEFAULT_PATTERNS,
...(await readIgnoreFile(flattenIgnorePath)),
...extraPatterns,
];
// De-duplicate

View File

@ -87,15 +87,15 @@ ide-configurations:
# 4. Rules are stored in .clinerules/ directory in your project
gemini:
name: Gemini CLI
rule-dir: .gemini/bmad-method/
format: single-file
command-suffix: .md
rule-dir: .gemini/commands/BMad/
format: multi-file
command-suffix: .toml
instructions: |
# To use BMad agents with the Gemini CLI:
# 1. The installer creates a .gemini/bmad-method/ directory in your project.
# 2. It concatenates all agent files into a single GEMINI.md file.
# 3. Simply mention the agent in your prompt (e.g., "As *dev, ...").
# 4. The Gemini CLI will automatically have the context for that agent.
# 1. The installer creates a `BMad` folder in `.gemini/commands`.
# 2. This adds custom commands for each agent and task.
# 3. Type /BMad:agents:<agent-name> (e.g., "/BMad:agents:dev", "/BMad:agents:pm") or /BMad:tasks:<task-name> (e.g., "/BMad:tasks:create-doc").
# 4. The agent will adopt that persona for the conversation or preform the task.
github-copilot:
name: Github Copilot
rule-dir: .github/chatmodes/

View File

@ -821,6 +821,7 @@ class IdeSetup extends BaseIdeSetup {
async getCoreTaskIds(installDir) {
const allTaskIds = [];
const glob = require('glob');
// Check core tasks in .bmad-core or root only
let tasksDir = path.join(installDir, '.bmad-core', 'tasks');
@ -829,7 +830,6 @@ class IdeSetup extends BaseIdeSetup {
}
if (await fileManager.pathExists(tasksDir)) {
const glob = require('glob');
const taskFiles = glob.sync('*.md', { cwd: tasksDir });
allTaskIds.push(...taskFiles.map((file) => path.basename(file, '.md')));
}
@ -837,6 +837,7 @@ class IdeSetup extends BaseIdeSetup {
// Check common tasks
const commonTasksDir = path.join(installDir, 'common', 'tasks');
if (await fileManager.pathExists(commonTasksDir)) {
const glob = require('glob');
const commonTaskFiles = glob.sync('*.md', { cwd: commonTasksDir });
allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, '.md')));
}
@ -1339,97 +1340,77 @@ class IdeSetup extends BaseIdeSetup {
return true;
}
async setupGeminiCli(installDir) {
const geminiDir = path.join(installDir, '.gemini');
const bmadMethodDir = path.join(geminiDir, 'bmad-method');
await fileManager.ensureDirectory(bmadMethodDir);
async setupGeminiCli(installDir, selectedAgent) {
const ideConfig = await configLoader.getIdeConfiguration('gemini');
const bmadCommandsDir = path.join(installDir, ideConfig['rule-dir']);
// Update logic for existing settings.json
const settingsPath = path.join(geminiDir, 'settings.json');
if (await fileManager.pathExists(settingsPath)) {
try {
const settingsContent = await fileManager.readFile(settingsPath);
const settings = JSON.parse(settingsContent);
let updated = false;
// Handle contextFileName property
if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
const originalLength = settings.contextFileName.length;
settings.contextFileName = settings.contextFileName.filter(
(fileName) => !fileName.startsWith('agents/'),
);
if (settings.contextFileName.length !== originalLength) {
updated = true;
}
}
if (updated) {
await fileManager.writeFile(settingsPath, JSON.stringify(settings, null, 2));
console.log(
chalk.green('✓ Updated .gemini/settings.json - removed agent file references'),
);
}
} catch (error) {
console.warn(chalk.yellow('Could not update .gemini/settings.json'), error);
}
}
// Remove old agents directory
const agentsDir = path.join(geminiDir, 'agents');
if (await fileManager.pathExists(agentsDir)) {
await fileManager.removeDirectory(agentsDir);
console.log(chalk.green('✓ Removed old .gemini/agents directory'));
}
// Get all available agents
const agents = await this.getAllAgentIds(installDir);
let concatenatedContent = '';
const agentCommandsDir = path.join(bmadCommandsDir, 'agents');
const taskCommandsDir = path.join(bmadCommandsDir, 'tasks');
await fileManager.ensureDirectory(agentCommandsDir);
await fileManager.ensureDirectory(taskCommandsDir);
// Process Agents
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
for (const agentId of agents) {
// Find the source agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
// Create properly formatted agent rule content (similar to trae)
let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
agentId,
installDir,
)} agent persona.\n\n`;
agentRuleContent += '## Agent Activation\n\n';
agentRuleContent +=
'CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n';
agentRuleContent += '```yaml\n';
// Extract just the YAML content from the agent file
const yamlContent = extractYamlFromAgent(agentContent);
if (yamlContent) {
agentRuleContent += yamlContent;
} else {
// If no YAML found, include the whole content minus the header
agentRuleContent += agentContent.replace(/^#.*$/m, '').trim();
}
agentRuleContent += '\n```\n\n';
agentRuleContent += '## File Reference\n\n';
const relativePath = path.relative(installDir, agentPath).replaceAll('\\', '/');
agentRuleContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
agentRuleContent += '## Usage\n\n';
agentRuleContent += `When the user types \`*${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
installDir,
)} persona and follow all instructions defined in the YAML configuration above.\n`;
// Add to concatenated content with separator
concatenatedContent += agentRuleContent + '\n\n---\n\n';
console.log(chalk.green(`✓ Added context for @${agentId}`));
}
if (!agentPath) {
console.log(chalk.yellow(`✗ Agent file not found for ${agentId}, skipping.`));
continue;
}
// Write the concatenated content to GEMINI.md
const geminiMdPath = path.join(bmadMethodDir, 'GEMINI.md');
await fileManager.writeFile(geminiMdPath, concatenatedContent);
console.log(chalk.green(`\n✓ Created GEMINI.md in ${bmadMethodDir}`));
const agentTitle = await this.getAgentTitle(agentId, installDir);
const commandPath = path.join(agentCommandsDir, `${agentId}.toml`);
// Get relative path from installDir to agent file for @{file} reference
const relativeAgentPath = path.relative(installDir, agentPath).replaceAll('\\', '/');
const tomlContent = `description = "Activates the ${agentTitle} agent from the BMad Method."
prompt = """
CRITICAL: You are now the BMad '${agentTitle}' agent. Adopt its persona, follow its instructions, and use its capabilities. The full agent definition is below.
@{${relativeAgentPath}}
"""`;
await fileManager.writeFile(commandPath, tomlContent);
console.log(chalk.green(`✓ Created agent command: /bmad:agents:${agentId}`));
}
// Process Tasks
const tasks = await this.getAllTaskIds(installDir);
for (const taskId of tasks) {
const taskPath = await this.findTaskPath(taskId, installDir);
if (!taskPath) {
console.log(chalk.yellow(`✗ Task file not found for ${taskId}, skipping.`));
continue;
}
const taskTitle = taskId
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
const commandPath = path.join(taskCommandsDir, `${taskId}.toml`);
// Get relative path from installDir to task file for @{file} reference
const relativeTaskPath = path.relative(installDir, taskPath).replaceAll('\\', '/');
const tomlContent = `description = "Executes the BMad Task: ${taskTitle}"
prompt = """
CRITICAL: You are to execute the BMad Task defined below.
@{${relativeTaskPath}}
"""`;
await fileManager.writeFile(commandPath, tomlContent);
console.log(chalk.green(`✓ Created task command: /bmad:tasks:${taskId}`));
}
console.log(
chalk.green(`
Created Gemini CLI extension in ${bmadCommandsDir}`),
);
console.log(
chalk.dim('You can now use commands like /bmad:agents:dev or /bmad:tasks:create-doc.'),
);
return true;
}