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
|
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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue