Compare commits
No commits in common. "1ad1f91e382f5b6d2547b93d9a85e0aea5b31a93" and "be85e5b4a01664f2f4a2a80c9960f65bb30f8b22" have entirely different histories.
1ad1f91e38
...
be85e5b4a0
|
|
@ -55,8 +55,7 @@ Load {planning_artifacts}/epics.md and review:
|
|||
2. **Requirements Grouping**: Group related FRs that deliver cohesive user outcomes
|
||||
3. **Incremental Delivery**: Each epic should deliver value independently
|
||||
4. **Logical Flow**: Natural progression from user's perspective
|
||||
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
|
||||
5. **🔗 Dependency-Free Within Epic**: Stories within an epic must NOT depend on future stories
|
||||
|
||||
**⚠️ CRITICAL PRINCIPLE:**
|
||||
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 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:**
|
||||
|
||||
- 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
|
||||
|
||||
**Step A: Assess Context and Identify 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:
|
||||
**Step A: Identify User Value Themes**
|
||||
|
||||
- Look for natural groupings in the FRs
|
||||
- Identify user journeys or workflows
|
||||
- Consider user types and their goals
|
||||
|
||||
**Step B: Propose Epic Structure**
|
||||
|
||||
For each proposed epic (considering whether epics share the same core files):
|
||||
For each proposed epic:
|
||||
|
||||
1. **Epic Title**: User-centric, value-focused
|
||||
2. **User Outcome**: What users can accomplish after this epic
|
||||
3. **FR Coverage**: Which FR numbers this epic addresses
|
||||
4. **Implementation Notes**: Any technical or UX considerations
|
||||
|
||||
**Step C: Review for File Overlap**
|
||||
|
||||
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**
|
||||
**Step C: Create the epics_list**
|
||||
|
||||
Format the epics_list as:
|
||||
|
||||
|
|
|
|||
|
|
@ -90,12 +90,6 @@ Review the complete epic and story breakdown to ensure EVERY FR is covered:
|
|||
- Dependencies flow naturally
|
||||
- Foundation stories only setup what's needed
|
||||
- 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const yaml = require('yaml');
|
||||
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
|
||||
* cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal
|
||||
* 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>/
|
||||
* and are resolved by walking the cache and matching `code` or `name` from the
|
||||
* 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.
|
||||
* 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
|
||||
* ExternalModuleManager.findExternalModuleSource but performs no git/network
|
||||
* 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');
|
||||
if (await fs.pathExists(builtIn)) return builtIn;
|
||||
|
||||
// Collect every module.yaml under a root using the standard candidate paths.
|
||||
// Url-source repos can host multiple plugins (discovery mode), so we need all
|
||||
// matches, not just the first. Returned in priority order.
|
||||
async function searchRootAll(root) {
|
||||
const results = [];
|
||||
// Search a resolved root directory using the same candidate-path pattern.
|
||||
async function searchRoot(root) {
|
||||
for (const dir of ['skills', 'src']) {
|
||||
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);
|
||||
if (await fs.pathExists(dirPath)) {
|
||||
|
|
@ -118,7 +111,7 @@ async function resolveInstalledModuleYaml(moduleName) {
|
|||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
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) {
|
||||
if (!entry.isDirectory() || !entry.name.endsWith('-setup')) continue;
|
||||
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');
|
||||
if (await fs.pathExists(atRoot)) results.push(atRoot);
|
||||
return results;
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (await fs.pathExists(atRoot)) return atRoot;
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheRoot = getExternalModuleCachePath(moduleName);
|
||||
|
|
@ -164,37 +150,6 @@ async function resolveInstalledModuleYaml(moduleName) {
|
|||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue