bmad-method 4.43.0 → 4.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +2 -9
- package/README.md +0 -80
- package/bmad-core/tasks/validate-next-story.md +1 -1
- package/dist/agents/dev.txt +1 -1
- package/dist/agents/po.txt +1 -1
- package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +1 -1
- package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +1 -1
- package/dist/expansion-packs/bmad-godot-game-dev/agents/bmad-orchestrator.txt +1513 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-analyst.txt +3190 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-architect.txt +4499 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-designer.txt +3925 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-developer.txt +666 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-pm.txt +2381 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-po.txt +1612 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-qa.txt +1745 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-sm.txt +1208 -0
- package/dist/expansion-packs/bmad-godot-game-dev/agents/game-ux-expert.txt +958 -0
- package/dist/expansion-packs/bmad-godot-game-dev/teams/godot-game-team.txt +27721 -0
- package/dist/teams/team-all.txt +1 -1
- package/dist/teams/team-fullstack.txt +1 -1
- package/dist/teams/team-ide-minimal.txt +1 -1
- package/dist/teams/team-no-ui.txt +1 -1
- package/docs/GUIDING-PRINCIPLES.md +3 -3
- package/docs/flattener.md +91 -0
- package/docs/versions.md +1 -1
- package/docs/working-in-the-brownfield.md +15 -6
- package/expansion-packs/bmad-godot-game-dev/README.md +244 -0
- package/expansion-packs/bmad-godot-game-dev/agent-teams/godot-game-team.yaml +18 -0
- package/expansion-packs/bmad-godot-game-dev/agents/bmad-orchestrator.md +147 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-analyst.md +84 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-architect.md +146 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-designer.md +78 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-developer.md +124 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-pm.md +82 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-po.md +115 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-qa.md +160 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-sm.md +66 -0
- package/expansion-packs/bmad-godot-game-dev/agents/game-ux-expert.md +75 -0
- package/expansion-packs/bmad-godot-game-dev/checklists/game-architect-checklist.md +377 -0
- package/expansion-packs/bmad-godot-game-dev/checklists/game-change-checklist.md +250 -0
- package/expansion-packs/bmad-godot-game-dev/checklists/game-design-checklist.md +225 -0
- package/expansion-packs/bmad-godot-game-dev/checklists/game-po-checklist.md +448 -0
- package/expansion-packs/bmad-godot-game-dev/checklists/game-story-dod-checklist.md +202 -0
- package/expansion-packs/bmad-godot-game-dev/config.yaml +30 -0
- package/expansion-packs/bmad-godot-game-dev/data/bmad-kb.md +811 -0
- package/expansion-packs/bmad-godot-game-dev/data/brainstorming-techniques.md +36 -0
- package/expansion-packs/bmad-godot-game-dev/data/development-guidelines.md +893 -0
- package/expansion-packs/bmad-godot-game-dev/data/elicitation-methods.md +156 -0
- package/expansion-packs/bmad-godot-game-dev/data/technical-preferences.md +3 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/advanced-elicitation.md +110 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/apply-qa-fixes.md +224 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/brownfield-create-epic.md +162 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/brownfield-create-story.md +149 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/correct-course-game.md +159 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/create-deep-research-prompt.md +278 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/create-doc.md +103 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/create-game-story.md +202 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/document-project.md +343 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/execute-checklist.md +88 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/facilitate-brainstorming-session.md +136 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/game-brownfield-create-epic.md +160 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/game-brownfield-create-story.md +147 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/game-design-brainstorming.md +290 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/game-risk-profile.md +368 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/game-test-design.md +219 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/generate-ai-frontend-prompt.md +51 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/kb-mode-interaction.md +77 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/review-game-story.md +364 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/shard-doc.md +187 -0
- package/expansion-packs/bmad-godot-game-dev/tasks/validate-game-story.md +208 -0
- package/expansion-packs/bmad-godot-game-dev/templates/brainstorming-output-tmpl.yaml +156 -0
- package/expansion-packs/bmad-godot-game-dev/templates/brownfield-prd-tmpl.yaml +281 -0
- package/expansion-packs/bmad-godot-game-dev/templates/competitor-analysis-tmpl.yaml +306 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-architecture-tmpl.yaml +1111 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-brief-tmpl.yaml +356 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-design-doc-tmpl.yaml +724 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-prd-tmpl.yaml +209 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-qa-gate-tmpl.yaml +186 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-story-tmpl.yaml +406 -0
- package/expansion-packs/bmad-godot-game-dev/templates/game-ui-spec-tmpl.yaml +601 -0
- package/expansion-packs/bmad-godot-game-dev/templates/level-design-doc-tmpl.yaml +620 -0
- package/expansion-packs/bmad-godot-game-dev/templates/market-research-tmpl.yaml +418 -0
- package/expansion-packs/bmad-godot-game-dev/utils/bmad-doc-template.md +327 -0
- package/expansion-packs/bmad-godot-game-dev/utils/workflow-management.md +71 -0
- package/expansion-packs/bmad-godot-game-dev/workflows/game-dev-greenfield.yaml +245 -0
- package/expansion-packs/bmad-godot-game-dev/workflows/game-prototype.yaml +213 -0
- package/package.json +1 -1
- package/release_notes.md +11 -2
- package/tools/flattener/ignoreRules.js +2 -0
- package/tools/installer/bin/bmad.js +2 -1
- package/tools/installer/config/install.config.yaml +16 -7
- package/tools/installer/lib/ide-setup.js +192 -80
- package/tools/installer/package.json +1 -1
- package/tools/upgraders/v3-to-v4-upgrader.js +1 -0
- package/test.md +0 -1
- /package/{implement-fork-friendly-ci.sh → tools/implement-fork-friendly-ci.sh} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
workflow:
|
|
2
|
+
id: godot-game-prototype
|
|
3
|
+
name: Godot Rapid Prototype Development
|
|
4
|
+
description: Godot-optimized workflow leveraging the engine's rapid prototyping features - @tool scripts, built-in nodes, CSG geometry, immediate mode GUI, and GDScript's duck typing. Emphasizes Godot's hot reload, in-editor testing, and scene-based iteration for validating game concepts in hours, not days.
|
|
5
|
+
type: prototype
|
|
6
|
+
project_types:
|
|
7
|
+
- godot-game-jam
|
|
8
|
+
- godot-mechanic-test
|
|
9
|
+
- godot-shader-demo
|
|
10
|
+
- godot-physics-sandbox
|
|
11
|
+
- godot-ui-experiment
|
|
12
|
+
- godot-multiplayer-test
|
|
13
|
+
prototype_sequence:
|
|
14
|
+
- step: concept_definition
|
|
15
|
+
agent: game-designer
|
|
16
|
+
duration: 15-30 minutes
|
|
17
|
+
creates: concept-summary.md
|
|
18
|
+
notes: Define concept leveraging Godot's strengths - built-in physics (Box2D/Bullet), particle systems, shaders, or procedural generation. Identify which Godot nodes will drive the core mechanic.
|
|
19
|
+
- step: rapid_design
|
|
20
|
+
agent: game-designer
|
|
21
|
+
duration: 30-60 minutes
|
|
22
|
+
creates: prototype-spec.md
|
|
23
|
+
requires: concept-summary.md
|
|
24
|
+
optional_steps:
|
|
25
|
+
- godot_node_selection
|
|
26
|
+
- scene_structure_sketch
|
|
27
|
+
- input_action_mapping
|
|
28
|
+
notes: Map mechanics to specific Godot nodes (Area2D, CharacterBody2D, RigidBody2D). Define scene hierarchy and signal connections. Plan InputMap actions for immediate responsiveness.
|
|
29
|
+
- step: technical_planning
|
|
30
|
+
agent: game-developer
|
|
31
|
+
duration: 15-30 minutes
|
|
32
|
+
creates: prototype-architecture.md
|
|
33
|
+
requires: prototype-spec.md
|
|
34
|
+
notes: Plan Godot-specific implementation - scene structure, autoload needs, @tool scripts for in-editor testing. Use GDScript for all prototype code (duck typing speeds iteration). Identify built-in nodes to leverage.
|
|
35
|
+
- step: implementation_stories
|
|
36
|
+
agent: game-sm
|
|
37
|
+
duration: 30-45 minutes
|
|
38
|
+
creates: prototype-stories/
|
|
39
|
+
requires: prototype-spec.md, prototype-architecture.md
|
|
40
|
+
notes: Create 3-5 Godot-focused stories - "Create player scene with CharacterBody2D", "Implement _physics_process movement", "Connect Area2D signals for interactions". Each story includes specific node types and Godot methods.
|
|
41
|
+
- step: iterative_development
|
|
42
|
+
agent: game-developer
|
|
43
|
+
duration: varies
|
|
44
|
+
implements: prototype-stories/
|
|
45
|
+
notes: Use Godot's hot reload and @tool scripts for real-time iteration. Test in editor with F6 (scene) and F5 (project). Profile with Godot's built-in monitors. Use Remote Debugger for mobile testing. Document which built-in nodes work best.
|
|
46
|
+
workflow_end:
|
|
47
|
+
action: prototype_evaluation
|
|
48
|
+
notes: "Prototype complete. Evaluate core mechanics, gather feedback, and decide next steps: iterate, expand, or archive."
|
|
49
|
+
game_jam_sequence:
|
|
50
|
+
- step: jam_concept
|
|
51
|
+
agent: game-designer
|
|
52
|
+
duration: 10-15 minutes
|
|
53
|
+
creates: jam-concept.md
|
|
54
|
+
notes: Match jam theme to Godot's built-in capabilities. Identify hero nodes (e.g., CPUParticles2D for effects, AudioStreamPlayer2D for dynamic audio). Define InputMap actions.
|
|
55
|
+
- step: jam_implementation
|
|
56
|
+
agent: game-developer
|
|
57
|
+
duration: varies (jam timeline)
|
|
58
|
+
creates: working-prototype
|
|
59
|
+
requires: jam-concept.md
|
|
60
|
+
notes: Build directly in Godot editor. Use built-in nodes, CSG for 3D prototypes, immediate GUI for quick UI. Leverage Godot's animation player for juice. Export to HTML5 for easy sharing. Keep scenes under 100 nodes for performance.
|
|
61
|
+
jam_workflow_end:
|
|
62
|
+
action: jam_submission
|
|
63
|
+
notes: Submit to game jam. Capture lessons learned and consider post-jam development if concept shows promise.
|
|
64
|
+
flow_diagram: |
|
|
65
|
+
```mermaid
|
|
66
|
+
graph TD
|
|
67
|
+
A[Start: Prototype Project] --> B{Development Context?}
|
|
68
|
+
B -->|Standard Prototype| C[game-designer: concept-summary.md]
|
|
69
|
+
B -->|Game Jam| D[game-designer: jam-concept.md]
|
|
70
|
+
|
|
71
|
+
C --> E[game-designer: prototype-spec.md]
|
|
72
|
+
E --> F[game-developer: prototype-architecture.md]
|
|
73
|
+
F --> G[game-sm: create prototype stories]
|
|
74
|
+
G --> H[game-developer: iterative implementation]
|
|
75
|
+
H --> I[Prototype Evaluation]
|
|
76
|
+
|
|
77
|
+
D --> J[game-developer: direct implementation]
|
|
78
|
+
J --> K[Game Jam Submission]
|
|
79
|
+
|
|
80
|
+
E -.-> E1[Optional: quick brainstorming]
|
|
81
|
+
E -.-> E2[Optional: reference research]
|
|
82
|
+
|
|
83
|
+
style I fill:#90EE90
|
|
84
|
+
style K fill:#90EE90
|
|
85
|
+
style C fill:#FFE4B5
|
|
86
|
+
style E fill:#FFE4B5
|
|
87
|
+
style F fill:#FFE4B5
|
|
88
|
+
style G fill:#FFE4B5
|
|
89
|
+
style H fill:#FFE4B5
|
|
90
|
+
style D fill:#FFB6C1
|
|
91
|
+
style J fill:#FFB6C1
|
|
92
|
+
```
|
|
93
|
+
godot_specific_features:
|
|
94
|
+
rapid_prototyping_tools:
|
|
95
|
+
- Tool scripts for in-editor testing without running
|
|
96
|
+
- CSG nodes for quick 3D geometry without modeling
|
|
97
|
+
- Immediate mode drawing for debug visualizations
|
|
98
|
+
- Built-in placeholder assets (icon.svg, default theme)
|
|
99
|
+
- Hot reload for GDScript changes
|
|
100
|
+
prototype_acceleration:
|
|
101
|
+
- F6 to test current scene instantly
|
|
102
|
+
- F5 to run full project
|
|
103
|
+
- Remote debugging on devices
|
|
104
|
+
- Live scene editing while running
|
|
105
|
+
- Inspector value tweaking in real-time
|
|
106
|
+
godot_node_combinations:
|
|
107
|
+
quick_player: CharacterBody2D + CollisionShape2D + Sprite2D
|
|
108
|
+
pickup_system: Area2D + signal connections
|
|
109
|
+
physics_toy: RigidBody2D + joints + constraints
|
|
110
|
+
particle_effects: CPUParticles2D with built-in parameters
|
|
111
|
+
ui_prototype: Control + containers + theme variations
|
|
112
|
+
export_for_testing:
|
|
113
|
+
- HTML5 export for easy web sharing
|
|
114
|
+
- One-click APK export for Android testing
|
|
115
|
+
- Debug export with remote debugger enabled
|
|
116
|
+
- PCK files for quick distribution
|
|
117
|
+
decision_guidance:
|
|
118
|
+
use_prototype_sequence_when:
|
|
119
|
+
- Testing Godot-specific features (shaders, particles, physics)
|
|
120
|
+
- Validating scene composition strategies
|
|
121
|
+
- Experimenting with Godot's node system combinations
|
|
122
|
+
- Building with Godot's animation tools (AnimationPlayer, AnimationTree)
|
|
123
|
+
- Testing Godot export targets (HTML5, Mobile, Desktop)
|
|
124
|
+
- Learning Godot's signal patterns and node communication
|
|
125
|
+
use_game_jam_sequence_when:
|
|
126
|
+
- Godot Game Jam participation
|
|
127
|
+
- Leveraging Godot's rapid development features
|
|
128
|
+
- Using CSG for quick 3D prototypes
|
|
129
|
+
- Building with Godot's immediate mode GUI
|
|
130
|
+
- Testing Godot's networking capabilities quickly
|
|
131
|
+
godot_prototype_best_practices:
|
|
132
|
+
godot_rapid_development:
|
|
133
|
+
- Use @tool scripts to test mechanics in editor without running
|
|
134
|
+
- Leverage Godot's hot reload for immediate feedback
|
|
135
|
+
- Build scenes incrementally with F6 (test current scene)
|
|
136
|
+
- Use placeholder Godot icons and CSG shapes
|
|
137
|
+
godot_node_leverage:
|
|
138
|
+
- Start with Godot's template projects when applicable
|
|
139
|
+
- Use Area2D for all detection/trigger needs
|
|
140
|
+
- Implement with CharacterBody2D's built-in movement methods
|
|
141
|
+
- Apply RigidBody2D for physics toys
|
|
142
|
+
- Use Control nodes with containers for auto-layout UI
|
|
143
|
+
godot_iteration_tools:
|
|
144
|
+
- Run scenes directly with F6 during development
|
|
145
|
+
- Use Godot's remote debugger for device testing
|
|
146
|
+
- Monitor performance with built-in profiler (not external tools)
|
|
147
|
+
- Adjust project settings in real-time
|
|
148
|
+
- Use editor's node property tweaking for balancing
|
|
149
|
+
godot_prototyping_patterns:
|
|
150
|
+
- Compose scenes, don't code everything
|
|
151
|
+
- Signal connections over hard references
|
|
152
|
+
- Export variables for in-editor tweaking
|
|
153
|
+
- AnimationPlayer for all timed events
|
|
154
|
+
- Resource files (.tres) for data-driven design
|
|
155
|
+
godot_prototype_evaluation:
|
|
156
|
+
godot_mechanic_validation:
|
|
157
|
+
- Does the mechanic work well with Godot's physics engine?
|
|
158
|
+
- Are Godot's built-in nodes sufficient or do we need custom?
|
|
159
|
+
- Can the mechanic scale with Godot's scene instancing?
|
|
160
|
+
- Does it perform well on Godot's HTML5 export?
|
|
161
|
+
godot_technical_assessment:
|
|
162
|
+
- Draw calls under 1000 (check Godot profiler)
|
|
163
|
+
- Physics bodies under 200 for mobile targets
|
|
164
|
+
- Scene tree depth reasonable (<10 levels)
|
|
165
|
+
- Proper use of Godot's threading if needed
|
|
166
|
+
- GDScript performance adequate or need C#/GDExtension?
|
|
167
|
+
godot_expansion_viability:
|
|
168
|
+
- Can current scene structure support more content?
|
|
169
|
+
- Are signals and groups set up for scaling?
|
|
170
|
+
- Is the resource system being used effectively?
|
|
171
|
+
- Would Godot's multiplayer API support this mechanic?
|
|
172
|
+
- Are we leveraging Godot's strengths (not fighting it)?
|
|
173
|
+
post_prototype_options:
|
|
174
|
+
iterate_and_improve:
|
|
175
|
+
action: continue_prototyping
|
|
176
|
+
when: Core mechanic shows promise but needs refinement
|
|
177
|
+
next_steps: Create new prototype iteration focusing on identified improvements
|
|
178
|
+
expand_to_full_game:
|
|
179
|
+
action: transition_to_full_development
|
|
180
|
+
when: Prototype validates strong game concept
|
|
181
|
+
next_steps: Use game-dev-greenfield workflow to create full game design and architecture
|
|
182
|
+
pivot_concept:
|
|
183
|
+
action: new_prototype_direction
|
|
184
|
+
when: Current mechanic doesn't work but insights suggest new direction
|
|
185
|
+
next_steps: Apply learnings to new prototype concept
|
|
186
|
+
archive_and_learn:
|
|
187
|
+
action: document_learnings
|
|
188
|
+
when: Prototype doesn't work but provides valuable insights
|
|
189
|
+
next_steps: Document lessons learned and move to next prototype concept
|
|
190
|
+
godot_time_boxing:
|
|
191
|
+
concept_phase: 30 min - Pick Godot nodes that drive your mechanic
|
|
192
|
+
design_phase: 1 hour - Sketch scene tree and signal flow
|
|
193
|
+
planning_phase: 30 min - Set up Godot project with right settings
|
|
194
|
+
implementation_phase: 2-hour sprints - F6 test after each sprint
|
|
195
|
+
polish_phase: 1 hour - Godot's animation tools for juice
|
|
196
|
+
godot_success_metrics:
|
|
197
|
+
godot_velocity:
|
|
198
|
+
- First scene running in Godot within 2 hours
|
|
199
|
+
- Core nodes connected and signaling within 4 hours
|
|
200
|
+
- Playable build exported (HTML5) within 8 hours
|
|
201
|
+
- All built-in Godot features identified for mechanic
|
|
202
|
+
godot_learning:
|
|
203
|
+
- Which Godot nodes best serve the mechanic
|
|
204
|
+
- Performance profile from Godot's monitors
|
|
205
|
+
- Export size and load time benchmarks
|
|
206
|
+
- Godot-specific optimizations discovered
|
|
207
|
+
- Editor workflow improvements identified
|
|
208
|
+
handoff_prompts:
|
|
209
|
+
concept_to_design: Concept defined with target Godot nodes identified. Create design spec mapping mechanics to Godot's systems.
|
|
210
|
+
design_to_technical: Design ready with scene structure planned. Create Godot project setup and technical approach.
|
|
211
|
+
technical_to_stories: Godot architecture defined. Create stories with specific node types and signal connections.
|
|
212
|
+
stories_to_implementation: Stories specify Godot implementation. Begin building in editor with F6 testing.
|
|
213
|
+
prototype_to_evaluation: Prototype running in Godot. Check profiler metrics and evaluate for expansion.
|
package/package.json
CHANGED
package/release_notes.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
## 🚀 What's New in v4.
|
|
1
|
+
## 🚀 What's New in v4.44.0
|
|
2
2
|
|
|
3
3
|
### ✨ New Features
|
|
4
4
|
- feat: implement fork-friendly CI/CD with opt-in mechanism (#476)
|
|
5
5
|
- feat(installer): add Codex CLI + Codex Web modes, generate AGENTS.md, inject npm scripts, and docs (#529)
|
|
6
6
|
- feat: add PR validation workflow and contribution checks
|
|
7
7
|
- feat: Add Auggie CLI (Augment Code) Integration (#520)
|
|
8
|
+
- feat: enhance file exclusion capabilities with .bmad-flattenignore support (#531)
|
|
9
|
+
- feat: add iflow cli support to bmad installer. (#510)
|
|
8
10
|
|
|
9
11
|
### 🐛 Bug Fixes
|
|
10
12
|
- fix: update installer version display to show 4.39.0
|
|
@@ -14,6 +16,9 @@
|
|
|
14
16
|
- fix: remove incorrect else branch causing flatten command regression (#452)
|
|
15
17
|
- fix: correct dependency path format in bmad-master agent (#495)
|
|
16
18
|
- fix: Codex options missing from the IDE selection menu (#535)
|
|
19
|
+
- Fixed: "glob" is not defined (#504)
|
|
20
|
+
- fix: Template file extension in validation next story steps (#523)
|
|
21
|
+
- fix: update .gitignore to correct cursor file entry (#485)
|
|
17
22
|
|
|
18
23
|
### 📦 Other Changes
|
|
19
24
|
- patch: move script to tools folder
|
|
@@ -22,6 +27,10 @@
|
|
|
22
27
|
- test: trigger PR validation (#533)
|
|
23
28
|
- docs: remove misplaced Codex section from README
|
|
24
29
|
- Expansion pack doc correction
|
|
30
|
+
- added gemini cli custom commands! (#549)
|
|
31
|
+
- Update ide-setup.js - add missing glob require (#514)
|
|
32
|
+
- Godot Game Dev expansion pack for BMAD (#532)
|
|
33
|
+
- documentation updates
|
|
25
34
|
|
|
26
35
|
### 🔧 Maintenance
|
|
27
36
|
- chore: bump to 4.39.1 to fix installer version display
|
|
@@ -36,4 +45,4 @@
|
|
|
36
45
|
npx bmad-method install
|
|
37
46
|
```
|
|
38
47
|
|
|
39
|
-
**Full Changelog**: https://github.com/bmadcode/BMAD-METHOD/compare/v4.39.0...v4.
|
|
48
|
+
**Full Changelog**: https://github.com/bmadcode/BMAD-METHOD/compare/v4.39.0...v4.44.0
|
|
@@ -154,9 +154,11 @@ async function parseGitignore(gitignorePath) {
|
|
|
154
154
|
async function loadIgnore(rootDir, extraPatterns = []) {
|
|
155
155
|
const ig = ignore();
|
|
156
156
|
const gitignorePath = path.join(rootDir, '.gitignore');
|
|
157
|
+
const flattenIgnorePath = path.join(rootDir, '.bmad-flattenignore');
|
|
157
158
|
const patterns = [
|
|
158
159
|
...(await readIgnoreFile(gitignorePath)),
|
|
159
160
|
...DEFAULT_PATTERNS,
|
|
161
|
+
...(await readIgnoreFile(flattenIgnorePath)),
|
|
160
162
|
...extraPatterns,
|
|
161
163
|
];
|
|
162
164
|
// De-duplicate
|
|
@@ -49,7 +49,7 @@ program
|
|
|
49
49
|
.option('-d, --directory <path>', 'Installation directory')
|
|
50
50
|
.option(
|
|
51
51
|
'-i, --ide <ide...>',
|
|
52
|
-
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, other)',
|
|
52
|
+
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, other)',
|
|
53
53
|
)
|
|
54
54
|
.option(
|
|
55
55
|
'-e, --expansion-packs <packs...>',
|
|
@@ -397,6 +397,7 @@ async function promptInstallation() {
|
|
|
397
397
|
choices: [
|
|
398
398
|
{ name: 'Cursor', value: 'cursor' },
|
|
399
399
|
{ name: 'Claude Code', value: 'claude-code' },
|
|
400
|
+
{ name: 'iFlow CLI', value: 'iflow-cli' },
|
|
400
401
|
{ name: 'Windsurf', value: 'windsurf' },
|
|
401
402
|
{ name: 'Trae', value: 'trae' }, // { name: 'Trae', value: 'trae'}
|
|
402
403
|
{ name: 'Roo Code', value: 'roo' },
|
|
@@ -28,6 +28,15 @@ ide-configurations:
|
|
|
28
28
|
# To use BMad agents in Claude Code:
|
|
29
29
|
# 1. Type /agent-name (e.g., "/dev", "/pm", "/architect")
|
|
30
30
|
# 2. Claude will switch to that agent's persona
|
|
31
|
+
iflow-cli:
|
|
32
|
+
name: iFlow CLI
|
|
33
|
+
rule-dir: .iflow/commands/BMad/
|
|
34
|
+
format: multi-file
|
|
35
|
+
command-suffix: .md
|
|
36
|
+
instructions: |
|
|
37
|
+
# To use BMad agents in iFlow CLI:
|
|
38
|
+
# 1. Type /agent-name (e.g., "/dev", "/pm", "/architect")
|
|
39
|
+
# 2. iFlow will switch to that agent's persona
|
|
31
40
|
crush:
|
|
32
41
|
name: Crush
|
|
33
42
|
rule-dir: .crush/commands/BMad/
|
|
@@ -78,15 +87,15 @@ ide-configurations:
|
|
|
78
87
|
# 4. Rules are stored in .clinerules/ directory in your project
|
|
79
88
|
gemini:
|
|
80
89
|
name: Gemini CLI
|
|
81
|
-
rule-dir: .gemini/
|
|
82
|
-
format:
|
|
83
|
-
command-suffix: .
|
|
90
|
+
rule-dir: .gemini/commands/BMad/
|
|
91
|
+
format: multi-file
|
|
92
|
+
command-suffix: .toml
|
|
84
93
|
instructions: |
|
|
85
94
|
# To use BMad agents with the Gemini CLI:
|
|
86
|
-
# 1. The installer creates a
|
|
87
|
-
# 2.
|
|
88
|
-
# 3.
|
|
89
|
-
# 4. The
|
|
95
|
+
# 1. The installer creates a `BMad` folder in `.gemini/commands`.
|
|
96
|
+
# 2. This adds custom commands for each agent and task.
|
|
97
|
+
# 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").
|
|
98
|
+
# 4. The agent will adopt that persona for the conversation or preform the task.
|
|
90
99
|
github-copilot:
|
|
91
100
|
name: Github Copilot
|
|
92
101
|
rule-dir: .github/chatmodes/
|
|
@@ -47,6 +47,9 @@ class IdeSetup extends BaseIdeSetup {
|
|
|
47
47
|
case 'claude-code': {
|
|
48
48
|
return this.setupClaudeCode(installDir, selectedAgent);
|
|
49
49
|
}
|
|
50
|
+
case 'iflow-cli': {
|
|
51
|
+
return this.setupIFlowCli(installDir, selectedAgent);
|
|
52
|
+
}
|
|
50
53
|
case 'crush': {
|
|
51
54
|
return this.setupCrush(installDir, selectedAgent);
|
|
52
55
|
}
|
|
@@ -453,6 +456,134 @@ class IdeSetup extends BaseIdeSetup {
|
|
|
453
456
|
console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
|
|
454
457
|
}
|
|
455
458
|
|
|
459
|
+
async setupIFlowCli(installDir, selectedAgent) {
|
|
460
|
+
// Setup bmad-core commands
|
|
461
|
+
const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
|
|
462
|
+
const coreAgents = selectedAgent ? [selectedAgent] : await this.getCoreAgentIds(installDir);
|
|
463
|
+
const coreTasks = await this.getCoreTaskIds(installDir);
|
|
464
|
+
await this.setupIFlowCliForPackage(
|
|
465
|
+
installDir,
|
|
466
|
+
'core',
|
|
467
|
+
coreSlashPrefix,
|
|
468
|
+
coreAgents,
|
|
469
|
+
coreTasks,
|
|
470
|
+
'.bmad-core',
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
// Setup expansion pack commands
|
|
474
|
+
const expansionPacks = await this.getInstalledExpansionPacks(installDir);
|
|
475
|
+
for (const packInfo of expansionPacks) {
|
|
476
|
+
const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path);
|
|
477
|
+
const packAgents = await this.getExpansionPackAgents(packInfo.path);
|
|
478
|
+
const packTasks = await this.getExpansionPackTasks(packInfo.path);
|
|
479
|
+
|
|
480
|
+
if (packAgents.length > 0 || packTasks.length > 0) {
|
|
481
|
+
// Use the actual directory name where the expansion pack is installed
|
|
482
|
+
const rootPath = path.relative(installDir, packInfo.path);
|
|
483
|
+
await this.setupIFlowCliForPackage(
|
|
484
|
+
installDir,
|
|
485
|
+
packInfo.name,
|
|
486
|
+
packSlashPrefix,
|
|
487
|
+
packAgents,
|
|
488
|
+
packTasks,
|
|
489
|
+
rootPath,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async setupIFlowCliForPackage(installDir, packageName, slashPrefix, agentIds, taskIds, rootPath) {
|
|
498
|
+
const commandsBaseDir = path.join(installDir, '.iflow', 'commands', slashPrefix);
|
|
499
|
+
const agentsDir = path.join(commandsBaseDir, 'agents');
|
|
500
|
+
const tasksDir = path.join(commandsBaseDir, 'tasks');
|
|
501
|
+
|
|
502
|
+
// Ensure directories exist
|
|
503
|
+
await fileManager.ensureDirectory(agentsDir);
|
|
504
|
+
await fileManager.ensureDirectory(tasksDir);
|
|
505
|
+
|
|
506
|
+
// Setup agents
|
|
507
|
+
for (const agentId of agentIds) {
|
|
508
|
+
// Find the agent file - for expansion packs, prefer the expansion pack version
|
|
509
|
+
let agentPath;
|
|
510
|
+
if (packageName === 'core') {
|
|
511
|
+
// For core, use the normal search
|
|
512
|
+
agentPath = await this.findAgentPath(agentId, installDir);
|
|
513
|
+
} else {
|
|
514
|
+
// For expansion packs, first try to find the agent in the expansion pack directory
|
|
515
|
+
const expansionPackPath = path.join(installDir, rootPath, 'agents', `${agentId}.md`);
|
|
516
|
+
if (await fileManager.pathExists(expansionPackPath)) {
|
|
517
|
+
agentPath = expansionPackPath;
|
|
518
|
+
} else {
|
|
519
|
+
// Fall back to core if not found in expansion pack
|
|
520
|
+
agentPath = await this.findAgentPath(agentId, installDir);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const commandPath = path.join(agentsDir, `${agentId}.md`);
|
|
525
|
+
|
|
526
|
+
if (agentPath) {
|
|
527
|
+
// Create command file with agent content
|
|
528
|
+
let agentContent = await fileManager.readFile(agentPath);
|
|
529
|
+
|
|
530
|
+
// Replace {root} placeholder with the appropriate root path for this context
|
|
531
|
+
agentContent = agentContent.replaceAll('{root}', rootPath);
|
|
532
|
+
|
|
533
|
+
// Add command header
|
|
534
|
+
let commandContent = `# /${agentId} Command\n\n`;
|
|
535
|
+
commandContent += `When this command is used, adopt the following agent persona:\n\n`;
|
|
536
|
+
commandContent += agentContent;
|
|
537
|
+
|
|
538
|
+
await fileManager.writeFile(commandPath, commandContent);
|
|
539
|
+
console.log(chalk.green(`✓ Created agent command: /${agentId}`));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Setup tasks
|
|
544
|
+
for (const taskId of taskIds) {
|
|
545
|
+
// Find the task file - for expansion packs, prefer the expansion pack version
|
|
546
|
+
let taskPath;
|
|
547
|
+
if (packageName === 'core') {
|
|
548
|
+
// For core, use the normal search
|
|
549
|
+
taskPath = await this.findTaskPath(taskId, installDir);
|
|
550
|
+
} else {
|
|
551
|
+
// For expansion packs, first try to find the task in the expansion pack directory
|
|
552
|
+
const expansionPackPath = path.join(installDir, rootPath, 'tasks', `${taskId}.md`);
|
|
553
|
+
if (await fileManager.pathExists(expansionPackPath)) {
|
|
554
|
+
taskPath = expansionPackPath;
|
|
555
|
+
} else {
|
|
556
|
+
// Fall back to core if not found in expansion pack
|
|
557
|
+
taskPath = await this.findTaskPath(taskId, installDir);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const commandPath = path.join(tasksDir, `${taskId}.md`);
|
|
562
|
+
|
|
563
|
+
if (taskPath) {
|
|
564
|
+
// Create command file with task content
|
|
565
|
+
let taskContent = await fileManager.readFile(taskPath);
|
|
566
|
+
|
|
567
|
+
// Replace {root} placeholder with the appropriate root path for this context
|
|
568
|
+
taskContent = taskContent.replaceAll('{root}', rootPath);
|
|
569
|
+
|
|
570
|
+
// Add command header
|
|
571
|
+
let commandContent = `# /${taskId} Task\n\n`;
|
|
572
|
+
commandContent += `When this command is used, execute the following task:\n\n`;
|
|
573
|
+
commandContent += taskContent;
|
|
574
|
+
|
|
575
|
+
await fileManager.writeFile(commandPath, commandContent);
|
|
576
|
+
console.log(chalk.green(`✓ Created task command: /${taskId}`));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
console.log(
|
|
581
|
+
chalk.green(`\n✓ Created iFlow CLI commands for ${packageName} in ${commandsBaseDir}`),
|
|
582
|
+
);
|
|
583
|
+
console.log(chalk.dim(` - Agents in: ${agentsDir}`));
|
|
584
|
+
console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
|
|
585
|
+
}
|
|
586
|
+
|
|
456
587
|
async setupCrushForPackage(installDir, packageName, slashPrefix, agentIds, taskIds, rootPath) {
|
|
457
588
|
const commandsBaseDir = path.join(installDir, '.crush', 'commands', slashPrefix);
|
|
458
589
|
const agentsDir = path.join(commandsBaseDir, 'agents');
|
|
@@ -690,6 +821,7 @@ class IdeSetup extends BaseIdeSetup {
|
|
|
690
821
|
|
|
691
822
|
async getCoreTaskIds(installDir) {
|
|
692
823
|
const allTaskIds = [];
|
|
824
|
+
const glob = require('glob');
|
|
693
825
|
|
|
694
826
|
// Check core tasks in .bmad-core or root only
|
|
695
827
|
let tasksDir = path.join(installDir, '.bmad-core', 'tasks');
|
|
@@ -698,7 +830,6 @@ class IdeSetup extends BaseIdeSetup {
|
|
|
698
830
|
}
|
|
699
831
|
|
|
700
832
|
if (await fileManager.pathExists(tasksDir)) {
|
|
701
|
-
const glob = require('glob');
|
|
702
833
|
const taskFiles = glob.sync('*.md', { cwd: tasksDir });
|
|
703
834
|
allTaskIds.push(...taskFiles.map((file) => path.basename(file, '.md')));
|
|
704
835
|
}
|
|
@@ -706,6 +837,7 @@ class IdeSetup extends BaseIdeSetup {
|
|
|
706
837
|
// Check common tasks
|
|
707
838
|
const commonTasksDir = path.join(installDir, 'common', 'tasks');
|
|
708
839
|
if (await fileManager.pathExists(commonTasksDir)) {
|
|
840
|
+
const glob = require('glob');
|
|
709
841
|
const commonTaskFiles = glob.sync('*.md', { cwd: commonTasksDir });
|
|
710
842
|
allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, '.md')));
|
|
711
843
|
}
|
|
@@ -1208,97 +1340,77 @@ class IdeSetup extends BaseIdeSetup {
|
|
|
1208
1340
|
return true;
|
|
1209
1341
|
}
|
|
1210
1342
|
|
|
1211
|
-
async setupGeminiCli(installDir) {
|
|
1212
|
-
const
|
|
1213
|
-
const
|
|
1214
|
-
await fileManager.ensureDirectory(bmadMethodDir);
|
|
1215
|
-
|
|
1216
|
-
// Update logic for existing settings.json
|
|
1217
|
-
const settingsPath = path.join(geminiDir, 'settings.json');
|
|
1218
|
-
if (await fileManager.pathExists(settingsPath)) {
|
|
1219
|
-
try {
|
|
1220
|
-
const settingsContent = await fileManager.readFile(settingsPath);
|
|
1221
|
-
const settings = JSON.parse(settingsContent);
|
|
1222
|
-
let updated = false;
|
|
1343
|
+
async setupGeminiCli(installDir, selectedAgent) {
|
|
1344
|
+
const ideConfig = await configLoader.getIdeConfiguration('gemini');
|
|
1345
|
+
const bmadCommandsDir = path.join(installDir, ideConfig['rule-dir']);
|
|
1223
1346
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
(fileName) => !fileName.startsWith('agents/'),
|
|
1229
|
-
);
|
|
1230
|
-
if (settings.contextFileName.length !== originalLength) {
|
|
1231
|
-
updated = true;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1347
|
+
const agentCommandsDir = path.join(bmadCommandsDir, 'agents');
|
|
1348
|
+
const taskCommandsDir = path.join(bmadCommandsDir, 'tasks');
|
|
1349
|
+
await fileManager.ensureDirectory(agentCommandsDir);
|
|
1350
|
+
await fileManager.ensureDirectory(taskCommandsDir);
|
|
1234
1351
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
console.warn(chalk.yellow('Could not update .gemini/settings.json'), error);
|
|
1352
|
+
// Process Agents
|
|
1353
|
+
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
|
1354
|
+
for (const agentId of agents) {
|
|
1355
|
+
const agentPath = await this.findAgentPath(agentId, installDir);
|
|
1356
|
+
if (!agentPath) {
|
|
1357
|
+
console.log(chalk.yellow(`✗ Agent file not found for ${agentId}, skipping.`));
|
|
1358
|
+
continue;
|
|
1243
1359
|
}
|
|
1244
|
-
}
|
|
1245
1360
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (await fileManager.pathExists(agentsDir)) {
|
|
1249
|
-
await fileManager.removeDirectory(agentsDir);
|
|
1250
|
-
console.log(chalk.green('✓ Removed old .gemini/agents directory'));
|
|
1251
|
-
}
|
|
1361
|
+
const agentTitle = await this.getAgentTitle(agentId, installDir);
|
|
1362
|
+
const commandPath = path.join(agentCommandsDir, `${agentId}.toml`);
|
|
1252
1363
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
let concatenatedContent = '';
|
|
1364
|
+
// Get relative path from installDir to agent file for @{file} reference
|
|
1365
|
+
const relativeAgentPath = path.relative(installDir, agentPath).replaceAll('\\', '/');
|
|
1256
1366
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1367
|
+
const tomlContent = `description = "Activates the ${agentTitle} agent from the BMad Method."
|
|
1368
|
+
prompt = """
|
|
1369
|
+
CRITICAL: You are now the BMad '${agentTitle}' agent. Adopt its persona, follow its instructions, and use its capabilities. The full agent definition is below.
|
|
1260
1370
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1371
|
+
@{${relativeAgentPath}}
|
|
1372
|
+
"""`;
|
|
1263
1373
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
agentId,
|
|
1268
|
-
installDir,
|
|
1269
|
-
)} agent persona.\n\n`;
|
|
1270
|
-
agentRuleContent += '## Agent Activation\n\n';
|
|
1271
|
-
agentRuleContent +=
|
|
1272
|
-
'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';
|
|
1273
|
-
agentRuleContent += '```yaml\n';
|
|
1274
|
-
// Extract just the YAML content from the agent file
|
|
1275
|
-
const yamlContent = extractYamlFromAgent(agentContent);
|
|
1276
|
-
if (yamlContent) {
|
|
1277
|
-
agentRuleContent += yamlContent;
|
|
1278
|
-
} else {
|
|
1279
|
-
// If no YAML found, include the whole content minus the header
|
|
1280
|
-
agentRuleContent += agentContent.replace(/^#.*$/m, '').trim();
|
|
1281
|
-
}
|
|
1282
|
-
agentRuleContent += '\n```\n\n';
|
|
1283
|
-
agentRuleContent += '## File Reference\n\n';
|
|
1284
|
-
const relativePath = path.relative(installDir, agentPath).replaceAll('\\', '/');
|
|
1285
|
-
agentRuleContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
|
|
1286
|
-
agentRuleContent += '## Usage\n\n';
|
|
1287
|
-
agentRuleContent += `When the user types \`*${agentId}\`, activate this ${await this.getAgentTitle(
|
|
1288
|
-
agentId,
|
|
1289
|
-
installDir,
|
|
1290
|
-
)} persona and follow all instructions defined in the YAML configuration above.\n`;
|
|
1374
|
+
await fileManager.writeFile(commandPath, tomlContent);
|
|
1375
|
+
console.log(chalk.green(`✓ Created agent command: /bmad:agents:${agentId}`));
|
|
1376
|
+
}
|
|
1291
1377
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1378
|
+
// Process Tasks
|
|
1379
|
+
const tasks = await this.getAllTaskIds(installDir);
|
|
1380
|
+
for (const taskId of tasks) {
|
|
1381
|
+
const taskPath = await this.findTaskPath(taskId, installDir);
|
|
1382
|
+
if (!taskPath) {
|
|
1383
|
+
console.log(chalk.yellow(`✗ Task file not found for ${taskId}, skipping.`));
|
|
1384
|
+
continue;
|
|
1295
1385
|
}
|
|
1386
|
+
|
|
1387
|
+
const taskTitle = taskId
|
|
1388
|
+
.split('-')
|
|
1389
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
1390
|
+
.join(' ');
|
|
1391
|
+
const commandPath = path.join(taskCommandsDir, `${taskId}.toml`);
|
|
1392
|
+
|
|
1393
|
+
// Get relative path from installDir to task file for @{file} reference
|
|
1394
|
+
const relativeTaskPath = path.relative(installDir, taskPath).replaceAll('\\', '/');
|
|
1395
|
+
|
|
1396
|
+
const tomlContent = `description = "Executes the BMad Task: ${taskTitle}"
|
|
1397
|
+
prompt = """
|
|
1398
|
+
CRITICAL: You are to execute the BMad Task defined below.
|
|
1399
|
+
|
|
1400
|
+
@{${relativeTaskPath}}
|
|
1401
|
+
"""`;
|
|
1402
|
+
|
|
1403
|
+
await fileManager.writeFile(commandPath, tomlContent);
|
|
1404
|
+
console.log(chalk.green(`✓ Created task command: /bmad:tasks:${taskId}`));
|
|
1296
1405
|
}
|
|
1297
1406
|
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1407
|
+
console.log(
|
|
1408
|
+
chalk.green(`
|
|
1409
|
+
✓ Created Gemini CLI extension in ${bmadCommandsDir}`),
|
|
1410
|
+
);
|
|
1411
|
+
console.log(
|
|
1412
|
+
chalk.dim('You can now use commands like /bmad:agents:dev or /bmad:tasks:create-doc.'),
|
|
1413
|
+
);
|
|
1302
1414
|
|
|
1303
1415
|
return true;
|
|
1304
1416
|
}
|