Compare commits
2 Commits
be85e5b4a0
...
1ad1f91e38
| Author | SHA1 | Date |
|---|---|---|
|
|
1ad1f91e38 | |
|
|
350688df67 |
|
|
@ -55,7 +55,8 @@ 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:
|
||||||
|
|
@ -74,6 +75,18 @@ 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
|
||||||
|
|
@ -82,21 +95,38 @@ Organize by USER VALUE, not technical layers:
|
||||||
|
|
||||||
### 3. Design Epic Structure Collaboratively
|
### 3. Design Epic Structure Collaboratively
|
||||||
|
|
||||||
**Step A: Identify User Value Themes**
|
**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:
|
||||||
|
|
||||||
- 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: Create the epics_list**
|
**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**
|
||||||
|
|
||||||
Format the epics_list as:
|
Format the epics_list as:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,12 @@ 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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
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');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,8 +87,11 @@ 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).
|
||||||
* Local custom-source modules are not cached; their path is read from the
|
* Url-source custom modules are cloned into ~/.bmad/cache/custom-modules/<host>/<owner>/<repo>/
|
||||||
* CustomModuleManager resolution cache set during the same install run.
|
* 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.
|
||||||
* 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.
|
||||||
|
|
@ -99,11 +103,14 @@ 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;
|
||||||
|
|
||||||
// Search a resolved root directory using the same candidate-path pattern.
|
// Collect every module.yaml under a root using the standard candidate paths.
|
||||||
async function searchRoot(root) {
|
// 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 = [];
|
||||||
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)) return direct;
|
if (await fs.pathExists(direct)) results.push(direct);
|
||||||
|
|
||||||
const dirPath = path.join(root, dir);
|
const dirPath = path.join(root, dir);
|
||||||
if (await fs.pathExists(dirPath)) {
|
if (await fs.pathExists(dirPath)) {
|
||||||
|
|
@ -111,7 +118,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)) return nested;
|
if (await fs.pathExists(nested)) results.push(nested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,12 +128,19 @@ 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)) return setupAssets;
|
if (await fs.pathExists(setupAssets)) results.push(setupAssets);
|
||||||
}
|
}
|
||||||
|
|
||||||
const atRoot = path.join(root, 'module.yaml');
|
const atRoot = path.join(root, 'module.yaml');
|
||||||
if (await fs.pathExists(atRoot)) return atRoot;
|
if (await fs.pathExists(atRoot)) results.push(atRoot);
|
||||||
return null;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheRoot = getExternalModuleCachePath(moduleName);
|
const cacheRoot = getExternalModuleCachePath(moduleName);
|
||||||
|
|
@ -150,6 +164,37 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue