Compare commits

..

No commits in common. "1ad1f91e382f5b6d2547b93d9a85e0aea5b31a93" and "be85e5b4a01664f2f4a2a80c9960f65bb30f8b22" have entirely different histories.

3 changed files with 13 additions and 94 deletions

View File

@ -55,8 +55,7 @@ Load {planning_artifacts}/epics.md and review:
2. **Requirements Grouping**: Group related FRs that deliver cohesive user outcomes 2. **Requirements Grouping**: Group related FRs that deliver cohesive user outcomes
3. **Incremental Delivery**: Each epic should deliver value independently 3. **Incremental Delivery**: Each epic should deliver value independently
4. **Logical Flow**: Natural progression from user's perspective 4. **Logical Flow**: Natural progression from user's perspective
5. **Dependency-Free Within Epic**: Stories within an epic must NOT depend on future stories 5. **🔗 Dependency-Free Within Epic**: Stories within an epic must NOT depend on future stories
6. **Implementation Efficiency**: Consider consolidating epics that all modify the same core files into fewer epics
**⚠️ CRITICAL PRINCIPLE:** **⚠️ CRITICAL PRINCIPLE:**
Organize by USER VALUE, not technical layers: Organize by USER VALUE, not technical layers:
@ -75,18 +74,6 @@ Organize by USER VALUE, not technical layers:
- Epic 3: Frontend Components (creates reusable components) - **No user value** - Epic 3: Frontend Components (creates reusable components) - **No user value**
- Epic 4: Deployment Pipeline (CI/CD setup) - **No user value** - Epic 4: Deployment Pipeline (CI/CD setup) - **No user value**
**❌ WRONG Epic Examples (File Churn on Same Component):**
- Epic 1: File Upload (modifies model, controller, web form, web API)
- Epic 2: File Status (modifies model, controller, web form, web API)
- Epic 3: File Access permissions (modifies model, controller, web form, web API)
- All three epics touch the same files — consolidate into one epic with ordered stories
**✅ CORRECT Alternative:**
- Epic 1: File Management Enhancement (upload, status, permissions as stories within one epic)
- Rationale: Single component, fully pre-designed, no feedback loop between epics
**🔗 DEPENDENCY RULES:** **🔗 DEPENDENCY RULES:**
- Each epic must deliver COMPLETE functionality for its domain - Each epic must deliver COMPLETE functionality for its domain
@ -95,38 +82,21 @@ Organize by USER VALUE, not technical layers:
### 3. Design Epic Structure Collaboratively ### 3. Design Epic Structure Collaboratively
**Step A: Assess Context and Identify Themes** **Step A: Identify User Value Themes**
First, assess how much of the solution design is already validated (Architecture, UX, Test Design).
When the outcome is certain and direction changes between epics are unlikely, prefer fewer but larger epics.
Split into multiple epics when there is a genuine risk boundary or when early feedback could change direction
of following epics.
Then, identify user value themes:
- Look for natural groupings in the FRs - Look for natural groupings in the FRs
- Identify user journeys or workflows - Identify user journeys or workflows
- Consider user types and their goals - Consider user types and their goals
**Step B: Propose Epic Structure** **Step B: Propose Epic Structure**
For each proposed epic:
For each proposed epic (considering whether epics share the same core files):
1. **Epic Title**: User-centric, value-focused 1. **Epic Title**: User-centric, value-focused
2. **User Outcome**: What users can accomplish after this epic 2. **User Outcome**: What users can accomplish after this epic
3. **FR Coverage**: Which FR numbers this epic addresses 3. **FR Coverage**: Which FR numbers this epic addresses
4. **Implementation Notes**: Any technical or UX considerations 4. **Implementation Notes**: Any technical or UX considerations
**Step C: Review for File Overlap** **Step C: Create the epics_list**
Assess whether multiple proposed epics repeatedly target the same core files. If overlap is significant:
- Distinguish meaningful overlap (same component end-to-end) from incidental sharing
- Ask whether to consolidate into one epic with ordered stories
- If confirmed, merge the epic FRs into a single epic, preserving dependency flow: each story must still fit within
a single dev agent's context
**Step D: Create the epics_list**
Format the epics_list as: Format the epics_list as:

View File

@ -90,12 +90,6 @@ Review the complete epic and story breakdown to ensure EVERY FR is covered:
- Dependencies flow naturally - Dependencies flow naturally
- Foundation stories only setup what's needed - Foundation stories only setup what's needed
- No big upfront technical work - No big upfront technical work
- **File Churn Check:** Do multiple epics repeatedly modify the same core files?
- Assess whether the overlap pattern suggests unnecessary churn or is incidental
- If overlap is significant: Validate that splitting provides genuine value (risk mitigation, feedback loops, context size limits)
- If no justification for the split: Recommend consolidation into fewer epics
- ❌ WRONG: Multiple epics each modify the same core files with no feedback loop between them
- ✅ RIGHT: Epics target distinct files/components, OR consolidation was explicitly considered and rejected with rationale
### 5. Dependency Validation (CRITICAL) ### 5. Dependency Validation (CRITICAL)

View File

@ -1,6 +1,5 @@
const path = require('node:path'); const path = require('node:path');
const os = require('node:os'); const os = require('node:os');
const yaml = require('yaml');
const fs = require('./fs-native'); const fs = require('./fs-native');
/** /**
@ -87,11 +86,8 @@ function getExternalModuleCachePath(moduleName, ...segments) {
* Built-in modules (core, bmm) live under <src>. External official modules are * Built-in modules (core, bmm) live under <src>. External official modules are
* cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal * cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal
* layouts (some at src/module.yaml, some at skills/module.yaml, some nested). * layouts (some at src/module.yaml, some at skills/module.yaml, some nested).
* Url-source custom modules are cloned into ~/.bmad/cache/custom-modules/<host>/<owner>/<repo>/ * Local custom-source modules are not cached; their path is read from the
* and are resolved by walking the cache and matching `code` or `name` from the * CustomModuleManager resolution cache set during the same install run.
* discovered module.yaml. Local custom-source modules are not cached; their
* path is read from the CustomModuleManager resolution cache set during the
* same install run.
* This mirrors the candidate-path search in * This mirrors the candidate-path search in
* ExternalModuleManager.findExternalModuleSource but performs no git/network * ExternalModuleManager.findExternalModuleSource but performs no git/network
* work, which keeps it safe to call during manifest writing. * work, which keeps it safe to call during manifest writing.
@ -103,14 +99,11 @@ async function resolveInstalledModuleYaml(moduleName) {
const builtIn = path.join(getModulePath(moduleName), 'module.yaml'); const builtIn = path.join(getModulePath(moduleName), 'module.yaml');
if (await fs.pathExists(builtIn)) return builtIn; if (await fs.pathExists(builtIn)) return builtIn;
// Collect every module.yaml under a root using the standard candidate paths. // Search a resolved root directory using the same candidate-path pattern.
// Url-source repos can host multiple plugins (discovery mode), so we need all async function searchRoot(root) {
// matches, not just the first. Returned in priority order.
async function searchRootAll(root) {
const results = [];
for (const dir of ['skills', 'src']) { for (const dir of ['skills', 'src']) {
const direct = path.join(root, dir, 'module.yaml'); const direct = path.join(root, dir, 'module.yaml');
if (await fs.pathExists(direct)) results.push(direct); if (await fs.pathExists(direct)) return direct;
const dirPath = path.join(root, dir); const dirPath = path.join(root, dir);
if (await fs.pathExists(dirPath)) { if (await fs.pathExists(dirPath)) {
@ -118,7 +111,7 @@ async function resolveInstalledModuleYaml(moduleName) {
for (const entry of entries) { for (const entry of entries) {
if (!entry.isDirectory()) continue; if (!entry.isDirectory()) continue;
const nested = path.join(dirPath, entry.name, 'module.yaml'); const nested = path.join(dirPath, entry.name, 'module.yaml');
if (await fs.pathExists(nested)) results.push(nested); if (await fs.pathExists(nested)) return nested;
} }
} }
} }
@ -128,19 +121,12 @@ async function resolveInstalledModuleYaml(moduleName) {
for (const entry of rootEntries) { for (const entry of rootEntries) {
if (!entry.isDirectory() || !entry.name.endsWith('-setup')) continue; if (!entry.isDirectory() || !entry.name.endsWith('-setup')) continue;
const setupAssets = path.join(root, entry.name, 'assets', 'module.yaml'); const setupAssets = path.join(root, entry.name, 'assets', 'module.yaml');
if (await fs.pathExists(setupAssets)) results.push(setupAssets); if (await fs.pathExists(setupAssets)) return setupAssets;
} }
const atRoot = path.join(root, 'module.yaml'); const atRoot = path.join(root, 'module.yaml');
if (await fs.pathExists(atRoot)) results.push(atRoot); if (await fs.pathExists(atRoot)) return atRoot;
return results; return null;
}
// Backwards-compatible single-result variant for the existing external-cache
// and resolution-cache fallbacks (one module per root by construction).
async function searchRoot(root) {
const all = await searchRootAll(root);
return all.length > 0 ? all[0] : null;
} }
const cacheRoot = getExternalModuleCachePath(moduleName); const cacheRoot = getExternalModuleCachePath(moduleName);
@ -164,37 +150,6 @@ async function resolveInstalledModuleYaml(moduleName) {
// Resolution cache unavailable — continue // Resolution cache unavailable — continue
} }
// Fallback: url-source custom modules cloned to ~/.bmad/cache/custom-modules/.
// Walk every cached repo, enumerate ALL module.yaml files via searchRootAll
// (a single repo can host multiple plugins in discovery mode), and match by
// the yaml's `code` or `name` field. This works on re-install runs where
// _resolutionCache is empty and covers both discovery-mode (with marketplace.json)
// and direct-mode modules, since we identify repo roots by .bmad-source.json
// (written by cloneRepo) or .claude-plugin/ rather than by marketplace.json.
try {
const customCacheDir = path.join(os.homedir(), '.bmad', 'cache', 'custom-modules');
if (await fs.pathExists(customCacheDir)) {
const { CustomModuleManager } = require('./modules/custom-module-manager');
const customMgr = new CustomModuleManager();
const repoRoots = await customMgr._findCacheRepoRoots(customCacheDir);
for (const { repoPath } of repoRoots) {
const candidates = await searchRootAll(repoPath);
for (const candidate of candidates) {
try {
const parsed = yaml.parse(await fs.readFile(candidate, 'utf8'));
if (parsed && (parsed.code === moduleName || parsed.name === moduleName)) {
return candidate;
}
} catch {
// Malformed yaml — skip
}
}
}
}
} catch {
// Custom-modules cache walk failed — continue
}
return null; return null;
} }