Compare commits

...

22 Commits

Author SHA1 Message Date
Wendy Smoak 88ecebfc90
Merge f0ad64efc4 into d4b4bdfa12 2026-02-16 12:04:02 +08:00
Brian Madison d4b4bdfa12 docs: improve project-context documentation and fix folder structure
- Add explanation/how-to docs for project-context.md feature
- Update workflow-map Context Management section with comprehensive guidance
- Fix folder structure examples to show proper nested subdirectories
  (planning-artifacts/ and implementation-artifacts/ under _bmad-output/)
- Update established-projects.md with project-context creation step
- Standardize terminology: use "existing projects" instead of "brownfield"
2026-02-15 17:32:05 -06:00
Brian Madison d6cc8b060a refactor: rename skill folders to bmad-os- prefix
Standardizes all skill folder names with bmad-os- prefix:
- changelog-social → bmad-os-changelog-social
- draft-changelog → bmad-os-draft-changelog
- gh-triage → bmad-os-gh-triage
- release-module → bmad-os-release-module
2026-02-15 17:31:10 -06:00
Brian Madison ccaa88bb2d feat: add Diataxis style fixer skill
Adds bmad-os-diataxis-style-fix skill that automatically fixes
documentation to comply with the Diataxis framework and BMad
Method style guide rules.

- Includes Diataxis framework primer (4 document types)
- References main docs/_STYLE_GUIDE.md as single source of truth
- Detects doc type by folder location
- Applies fixes without committing (user reviews first)
2026-02-15 17:29:12 -06:00
Wendy Smoak f0ad64efc4 Handle Windows line endings like manifest-generator.js:175 2026-02-15 11:54:30 -05:00
Wendy Smoak decf15b5da Add more tests for Coderabbit 2026-02-15 11:27:49 -05:00
Wendy Smoak 64e5a9c696 guard against missing project directory 2026-02-15 10:12:49 -05:00
Wendy Smoak b318d9242e fix(codex): use yaml.stringify for skill frontmatter to escape special characters
Prevents invalid YAML when agentName or skillName contains quotes or
other special characters. Aligns the fallback path in transformToSkillFormat
and installCustomAgentLauncher with the existing yaml.stringify usage on
the main code path.
2026-02-15 10:03:54 -05:00
Wendy Smoak 6db629278a Use /Users/wsmoak instead of ~ 2026-02-15 09:49:16 -05:00
Wendy Smoak 94666bd05b
Merge branch 'main' into skills-for-codex 2026-02-15 09:37:23 -05:00
Wendy Smoak dfd961944c add more tests 2026-02-15 09:17:53 -05:00
Wendy Smoak d0ef58b421 Handle Windows line-endings 2026-02-15 09:06:16 -05:00
Wendy Smoak 4bd43ec8b9
Merge branch 'main' into skills-for-codex 2026-02-15 08:05:23 -05:00
Wendy Smoak 7f81518896 Replace description regex with yaml.parse/stringify in codex.js
Use the yaml library (already a project dependency) instead of a
hand-rolled regex to parse and re-serialize frontmatter descriptions,
matching the pattern used in manifest-generator.js. Update tests to
validate round-trip correctness rather than exact quoting style.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 07:13:30 -05:00
Wendy Smoak 43672d33c1 simplify test 2026-02-14 19:39:24 -05:00
Wendy Smoak 26dd80e021 Fix description regex to handle quotes in transformToSkillFormat
The regex that extracts descriptions from YAML frontmatter did not
properly parse quoted values. Descriptions containing apostrophes
(e.g. "can't") produced invalid YAML when re-wrapped in single quotes.

Replace the naive regex with proper handling of single-quoted,
double-quoted, and unquoted YAML values, and escape inner single
quotes using YAML '' syntax before re-wrapping.

Add tests for transformToSkillFormat covering plain, double-quoted,
and single-quoted descriptions with embedded apostrophes.
2026-02-14 19:31:04 -05:00
Wendy Smoak 746bd50d19 Add .agents directory to .gitignore 2026-02-14 19:19:02 -05:00
Wendy Smoak fcf781fa39 clean up comments 2026-02-14 19:07:49 -05:00
Wendy Smoak 44f07bf341 Clarify where Codex searches for skills 2026-02-14 19:05:09 -05:00
Wendy Smoak 79855b2c4c default to per-project instead of global 2026-02-14 18:51:53 -05:00
Wendy Smoak 62e0b0ba52 switch default to per-project skills to align with other tools 2026-02-14 18:31:14 -05:00
Wendy Smoak 18a4a489c8 feat(codex): convert installer from deprecated prompts to agentskills.io skills format
Codex CLI has deprecated custom prompts in favor of skills following the
agentskills.io specification. Updates the Codex installer to write
{name}/SKILL.md directories into .agents/skills/ instead of flat files
into .codex/prompts/. Removes CODEX_HOME requirement for project-scoped
installs since Codex auto-discovers .agents/skills/ directories.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:06:12 -05:00
26 changed files with 1123 additions and 117 deletions

View File

@ -0,0 +1,7 @@
---
name: bmad-os-diataxis-style-fix
description: Fixes documentation to comply with Diataxis framework and BMad Method style guide rules
disable-model-invocation: true
---
Read `prompts/instructions.md` and execute.

View File

@ -0,0 +1,229 @@
# Diataxis Style Fixer
Automatically fixes documentation to comply with the Diataxis framework and BMad Method style guide.
## CRITICAL RULES
- **NEVER commit or push changes** — let the user review first
- **NEVER make destructive edits** — preserve all content, only fix formatting
- **Use Edit tool** — make targeted fixes, not full file rewrites
- **Show summary** — after fixing, list all changes made
## Input
Documentation file path or directory to fix. Defaults to `docs/` if not specified.
## Step 1: Understand Diataxis Framework
**Diataxis** is a documentation framework that categorizes content into four types based on two axes:
| | **Learning** (oriented toward future) | **Doing** (oriented toward present) |
| -------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| **Practical** | **Tutorials** — lessons that guide learners through achieving a specific goal | **How-to guides** — step-by-step instructions for solving a specific problem |
| **Conceptual** | **Explanation** — content that clarifies and describes underlying concepts | **Reference** — technical descriptions, organized for lookup |
**Key principles:**
- Each document type serves a distinct user need
- Don't mix types — a tutorial shouldn't explain concepts deeply
- Focus on the user's goal, not exhaustive coverage
- Structure follows purpose (tutorials are linear, reference is scannable)
## Step 2: Read the Style Guide
Read the project's style guide at `docs/_STYLE_GUIDE.md` to understand all project-specific conventions.
## Step 3: Detect Document Type
Based on file location, determine the document type:
| Location | Diataxis Type |
| -------------------- | -------------------- |
| `/docs/tutorials/` | Tutorial |
| `/docs/how-to/` | How-to guide |
| `/docs/explanation/` | Explanation |
| `/docs/reference/` | Reference |
| `/docs/glossary/` | Reference (glossary) |
## Step 4: Find and Fix Issues
For each markdown file, scan for issues and fix them:
### Universal Fixes (All Doc Types)
**Horizontal Rules (`---`)**
- Remove any `---` outside of YAML frontmatter
- Replace with `##` section headers or admonitions as appropriate
**`####` Headers**
- Replace with bold text: `#### Header``**Header**`
- Or convert to admonition if it's a warning/notice
**"Related" or "Next:" Sections**
- Remove entire section including links
- The sidebar handles navigation
**Deeply Nested Lists**
- Break into sections with `##` headers
- Flatten to max 3 levels
**Code Blocks for Dialogue/Examples**
- Convert to admonitions:
```
:::note[Example]
[content]
:::
```
**Bold Paragraph Callouts**
- Convert to admonitions with appropriate type
**Too Many Admonitions**
- Limit to 1-2 per section (tutorials allow 3-4 per major section)
- Consolidate related admonitions
- Remove less critical ones if over limit
**Table Cells / List Items > 2 Sentences**
- Break into multiple rows/cells
- Or shorten to 1-2 sentences
**Header Budget Exceeded**
- Merge related sections
- Convert some `##` to `###` subsections
- Goal: 8-12 `##` per doc; 2-3 `###` per section
### Type-Specific Fixes
**Tutorials** (`/docs/tutorials/`)
- Ensure hook describes outcome in 1-2 sentences
- Add "What You'll Learn" bullet section if missing
- Add `:::note[Prerequisites]` if missing
- Add `:::tip[Quick Path]` TL;DR at top if missing
- Use tables for phases, commands, agents
- Add "What You've Accomplished" section if missing
- Add Quick Reference table if missing
- Add Common Questions section if missing
- Add Getting Help section if missing
- Add `:::tip[Key Takeaways]` at end if missing
**How-To** (`/docs/how-to/`)
- Ensure hook starts with "Use the `X` workflow to..."
- Add "When to Use This" with 3-5 bullets if missing
- Add `:::note[Prerequisites]` if missing
- Ensure steps are numbered `###` with action verbs
- Add "What You Get" describing outputs if missing
**Explanation** (`/docs/explanation/`)
- Ensure hook states what document explains
- Organize content into scannable `##` sections
- Add comparison tables for 3+ options
- Link to how-to guides for procedural questions
- Limit to 2-3 admonitions per document
**Reference** (`/docs/reference/`)
- Ensure hook states what document references
- Ensure structure matches reference type
- Use consistent item structure throughout
- Use tables for structured/comparative data
- Link to explanation docs for conceptual depth
- Limit to 1-2 admonitions per document
**Glossary** (`/docs/glossary/` or glossary files)
- Ensure categories as `##` headers
- Ensure terms in tables (not individual headers)
- Definitions 1-2 sentences max
- Bold term names in cells
## Step 5: Apply Fixes
For each file with issues:
1. Read the file
2. Use Edit tool for each fix
3. Track what was changed
## Step 6: Summary
After processing all files, output a summary:
```markdown
# Style Fixes Applied
**Files processed:** N
**Files modified:** N
## Changes Made
### `path/to/file.md`
- Removed horizontal rule at line 45
- Converted `####` headers to bold text
- Added `:::tip[Quick Path]` admonition
- Consolidated 3 admonitions into 2
### `path/to/other.md`
- Removed "Related:" section
- Fixed table cell length (broke into 2 rows)
## Review Required
Please review the changes. When satisfied, commit and push as needed.
```
## Common Patterns
**Converting `####` to bold:**
```markdown
#### Important Note
Some text here.
```
```markdown
**Important Note**
Some text here.
```
**Removing horizontal rule:**
```markdown
Some content above.
---
Some content below.
```
```markdown
Some content above.
## [Descriptive Section Header]
Some content below.
```
**Converting code block to admonition:**
```markdown
```
User: What should I do?
Agent: Run the workflow.
```
```
```markdown
:::note[Example]
**User:** What should I do?
**Agent:** Run the workflow.
:::
```
**Converting bold paragraph to admonition:**
```markdown
**IMPORTANT:** This is critical that you read this before proceeding.
```
```markdown
:::caution[Important]
This is critical that you read this before proceeding.
:::
```

1
.gitignore vendored
View File

@ -36,6 +36,7 @@ cursor
CLAUDE.local.md
.serena/
.claude/settings.local.json
.agents
z*/

View File

@ -77,7 +77,10 @@ Show in "What You've Accomplished" sections:
your-project/
├── _bmad/ # BMad configuration
├── _bmad-output/
│ ├── PRD.md # Your requirements document
│ ├── planning-artifacts/
│ │ └── PRD.md # Your requirements document
│ ├── implementation-artifacts/
│ └── project-context.md # Implementation rules (optional)
└── ...
```
````

View File

@ -0,0 +1,157 @@
---
title: "Project Context"
description: How project-context.md guides AI agents with your project's rules and preferences
sidebar:
order: 7
---
The `project-context.md` file is your project's implementation guide for AI agents. Similar to a "constitution" in other development systems, it captures the rules, patterns, and preferences that ensure consistent code generation across all workflows.
## What It Does
AI agents make implementation decisions constantly — which patterns to follow, how to structure code, what conventions to use. Without clear guidance, they may:
- Follow generic best practices that don't match your codebase
- Make inconsistent decisions across different stories
- Miss project-specific requirements or constraints
The `project-context.md` file solves this by documenting what agents need to know in a concise, LLM-optimized format.
## How It Works
Every implementation workflow automatically loads `project-context.md` if it exists. The architect workflow also loads it to respect your technical preferences when designing the architecture.
**Loaded by these workflows:**
- `create-architecture` — respects technical preferences during solutioning
- `create-story` — informs story creation with project patterns
- `dev-story` — guides implementation decisions
- `code-review` — validates against project standards
- `quick-dev` — applies patterns when implementing tech-specs
- `sprint-planning`, `retrospective`, `correct-course` — provides project-wide context
## When to Create It
The `project-context.md` file is useful at any stage of a project:
| Scenario | When to Create | Purpose |
|----------|----------------|---------|
| **New project, before architecture** | Manually, before `create-architecture` | Document your technical preferences so the architect respects them |
| **New project, after architecture** | Via `generate-project-context` or manually | Capture architecture decisions for implementation agents |
| **Existing project** | Via `generate-project-context` | Discover existing patterns so agents follow established conventions |
| **Quick Flow project** | Before or during `quick-dev` | Ensure quick implementation respects your patterns |
:::tip[Recommended]
For new projects, create it manually before architecture if you have strong technical preferences. Otherwise, generate it after architecture to capture those decisions.
:::
## What Goes In It
The file has two main sections:
### Technology Stack & Versions
Documents the frameworks, languages, and tools your project uses with specific versions:
```markdown
## Technology Stack & Versions
- Node.js 20.x, TypeScript 5.3, React 18.2
- State: Zustand (not Redux)
- Testing: Vitest, Playwright, MSW
- Styling: Tailwind CSS with custom design tokens
```
### Critical Implementation Rules
Documents patterns and conventions that agents might otherwise miss:
```markdown
## Critical Implementation Rules
**TypeScript Configuration:**
- Strict mode enabled — no `any` types without explicit approval
- Use `interface` for public APIs, `type` for unions/intersections
**Code Organization:**
- Components in `/src/components/` with co-located `.test.tsx`
- Utilities in `/src/lib/` for reusable pure functions
- API calls use the `apiClient` singleton — never fetch directly
**Testing Patterns:**
- Unit tests focus on business logic, not implementation details
- Integration tests use MSW to mock API responses
- E2E tests cover critical user journeys only
**Framework-Specific:**
- All async operations use the `handleError` wrapper for consistent error handling
- Feature flags accessed via `featureFlag()` from `@/lib/flags`
- New routes follow the file-based routing pattern in `/src/app/`
```
Focus on what's **unobvious** — things agents might not infer from reading code snippets. Don't document standard practices that apply universally.
## Creating the File
You have three options:
### Manual Creation
Create the file at `_bmad-output/project-context.md` and add your rules:
```bash
# In your project root
mkdir -p _bmad-output
touch _bmad-output/project-context.md
```
Edit it with your technology stack and implementation rules. The architect and implementation workflows will automatically find and load it.
### Generate After Architecture
Run the `generate-project-context` workflow after completing your architecture:
```bash
/bmad-bmm-generate-project-context
```
This scans your architecture document and project files to generate a context file capturing the decisions made.
### Generate for Existing Projects
For existing projects, run `generate-project-context` to discover existing patterns:
```bash
/bmad-bmm-generate-project-context
```
The workflow analyzes your codebase to identify conventions, then generates a context file you can review and refine.
## Why It Matters
Without `project-context.md`, agents make assumptions that may not match your project:
| Without Context | With Context |
|----------------|--------------|
| Uses generic patterns | Follows your established conventions |
| Inconsistent style across stories | Consistent implementation |
| May miss project-specific constraints | Respects all technical requirements |
| Each agent decides independently | All agents align with same rules |
This is especially important for:
- **Quick Flow** — skips PRD and architecture, so context file fills the gap
- **Team projects** — ensures all agents follow the same standards
- **Existing projects** — prevents breaking established patterns
## Editing and Updating
The `project-context.md` file is a living document. Update it when:
- Architecture decisions change
- New conventions are established
- Patterns evolve during implementation
- You identify gaps from agent behavior
You can edit it manually at any time, or re-run `generate-project-context` to update it after significant changes.
:::note[File Location]
The default location is `_bmad-output/project-context.md`. Workflows search for it there, and also check `**/project-context.md` anywhere in your project.
:::

View File

@ -5,7 +5,7 @@ sidebar:
order: 6
---
Use BMad Method effectively when working on existing projects and legacy codebases, sometimes also referred to as brownfield projects.
Use BMad Method effectively when working on existing projects and legacy codebases.
This guide covers the essential workflow for onboarding to existing projects with BMad Method.
@ -23,7 +23,30 @@ If you have completed all PRD epics and stories through the BMad process, clean
- `_bmad-output/planning-artifacts/`
- `_bmad-output/implementation-artifacts/`
## Step 2: Maintain Quality Project Documentation
## Step 2: Create Project Context
:::tip[Recommended for Existing Projects]
Generate `project-context.md` to capture your existing codebase patterns and conventions. This ensures AI agents follow your established practices when implementing changes.
:::
Run the generate project context workflow:
```bash
/bmad-bmm-generate-project-context
```
This scans your codebase to identify:
- Technology stack and versions
- Code organization patterns
- Naming conventions
- Testing approaches
- Framework-specific patterns
You can review and refine the generated file, or create it manually at `_bmad-output/project-context.md` if you prefer.
[Learn more about project context](../explanation/project-context.md)
## Step 3: Maintain Quality Project Documentation
Your `docs/` folder should contain succinct, well-organized documentation that accurately represents your project:

View File

@ -0,0 +1,136 @@
---
title: "Manage Project Context"
description: Create and maintain project-context.md to guide AI agents
sidebar:
order: 7
---
Use the `project-context.md` file to ensure AI agents follow your project's technical preferences and implementation rules throughout all workflows.
:::note[Prerequisites]
- BMad Method installed
- Understanding of your project's technology stack and conventions
:::
## When to Use This
- You have strong technical preferences before starting architecture
- You've completed architecture and want to capture decisions for implementation
- You're working on an existing codebase with established patterns
- You notice agents making inconsistent decisions across stories
## Step 1: Choose Your Approach
**Manual creation** — Best when you know exactly what rules you want to document
**Generate after architecture** — Best for capturing decisions made during solutioning
**Generate for existing projects** — Best for discovering patterns in existing codebases
## Step 2: Create the File
### Option A: Manual Creation
Create the file at `_bmad-output/project-context.md`:
```bash
mkdir -p _bmad-output
touch _bmad-output/project-context.md
```
Add your technology stack and implementation rules:
```markdown
---
project_name: 'MyProject'
user_name: 'YourName'
date: '2026-02-15'
sections_completed: ['technology_stack', 'critical_rules']
---
# Project Context for AI Agents
## Technology Stack & Versions
- Node.js 20.x, TypeScript 5.3, React 18.2
- State: Zustand
- Testing: Vitest, Playwright
- Styling: Tailwind CSS
## Critical Implementation Rules
**TypeScript:**
- Strict mode enabled, no `any` types
- Use `interface` for public APIs, `type` for unions
**Code Organization:**
- Components in `/src/components/` with co-located tests
- API calls use `apiClient` singleton — never fetch directly
**Testing:**
- Unit tests focus on business logic
- Integration tests use MSW for API mocking
```
### Option B: Generate After Architecture
Run the workflow in a fresh chat:
```bash
/bmad-bmm-generate-project-context
```
The workflow scans your architecture document and project files to generate a context file capturing the decisions made.
### Option C: Generate for Existing Projects
For existing projects, run:
```bash
/bmad-bmm-generate-project-context
```
The workflow analyzes your codebase to identify conventions, then generates a context file you can review and refine.
## Step 3: Verify Content
Review the generated file and ensure it captures:
- Correct technology versions
- Your actual conventions (not generic best practices)
- Rules that prevent common mistakes
- Framework-specific patterns
Edit manually to add anything missing or remove inaccuracies.
## What You Get
A `project-context.md` file that:
- Ensures all agents follow the same conventions
- Prevents inconsistent decisions across stories
- Captures architecture decisions for implementation
- Serves as a reference for your project's patterns and rules
## Tips
:::tip[Focus on the Unobvious]
Document patterns agents might miss such as "Use JSDoc style comments on every public class, function and variable", not universal practices like "use meaningful variable names" which LLMs know at this point.
:::
:::tip[Keep It Lean]
This file is loaded by every implementation workflow. Long files waste context. Do not include content that only applies to narrow scope or specific stories or features.
:::
:::tip[Update as Needed]
Edit manually when patterns change, or re-generate after significant architecture changes.
:::
:::tip[Works for All Project Types]
Just as useful for Quick Flow as for full BMad Method projects.
:::
## Next Steps
- [**Project Context Explanation**](../explanation/project-context.md) — Learn more about how it works
- [**Workflow Map**](../reference/workflow-map.md) — See which workflows load project context

View File

@ -23,11 +23,11 @@ Document sharding splits large markdown files into smaller, organized files base
```text
Before Sharding:
docs/
_bmad-output/planning-artifacts/
└── PRD.md (large 50k token file)
After Sharding:
docs/
_bmad-output/planning-artifacts/
└── prd/
├── index.md # Table of contents with descriptions
├── overview.md # Section 1

View File

@ -77,12 +77,44 @@ Skip phases 1-3 for small, well-understood work.
Each document becomes context for the next phase. The PRD tells the architect what constraints matter. The architecture tells the dev agent which patterns to follow. Story files give focused, complete context for implementation. Without this structure, agents make inconsistent decisions.
For established projects, `document-project` creates or updates `project-context.md` - what exists in the codebase and the rules all implementation workflows must observe. Run it just before Phase 4, and again when something significant changes - structure, architecture, or those rules. You can also edit `project-context.md` by hand.
### Project Context
All implementation workflows load `project-context.md` if it exists. Additional context per workflow:
:::tip[Recommended]
Create `project-context.md` to ensure AI agents follow your project's rules and preferences. This file works like a constitution for your project — it guides implementation decisions across all workflows.
:::
**When to create it:**
| Scenario | Approach |
|----------|----------|
| Before architecture (manual) | Document technical preferences you want the architect to respect |
| After architecture | Generate it to capture decisions made during solutioning |
| Existing projects | Run `generate-project-context` to discover established patterns |
| Quick Flow | Create before `quick-dev` to ensure consistent implementation |
**How to create it:**
- **Manually** — Create `_bmad-output/project-context.md` with your technology stack and implementation rules
- **Generate it** — Run `/bmad-bmm-generate-project-context` to auto-generate from your architecture or codebase
**What workflows load it:**
| Workflow | Purpose |
|----------|---------|
| `create-architecture` | Respects technical preferences when designing |
| `create-story` | Informs story creation with project patterns |
| `dev-story` | Guides implementation decisions |
| `code-review` | Validates against project standards |
| `quick-dev` | Applies patterns when implementing |
[**Learn more about project-context.md**](../explanation/project-context.md)
### Additional Context by Workflow
Beyond `project-context.md`, each workflow loads specific documents:
| Workflow | Also Loads |
| -------------- | ---------------------------- |
|----------|------------|
| `create-story` | epics, PRD, architecture, UX |
| `dev-story` | story file |
| `code-review` | architecture, story file |

View File

@ -79,6 +79,12 @@ Always start a fresh chat for each workflow. This prevents context limitations f
Work through phases 1-3. **Use fresh chats for each workflow.**
:::tip[Project Context (Optional)]
Before starting, consider creating `project-context.md` to document your technical preferences and implementation rules. This ensures all AI agents follow your conventions throughout the project.
Create it manually at `_bmad-output/project-context.md` or generate it after architecture using `/bmad-bmm-generate-project-context`. [Learn more](../explanation/project-context.md).
:::
### Phase 1: Analysis (Optional)
All workflows in this phase are optional:
@ -157,10 +163,13 @@ Your project now has:
your-project/
├── _bmad/ # BMad configuration
├── _bmad-output/
│ ├── PRD.md # Your requirements document
│ ├── architecture.md # Technical decisions
│ ├── epics/ # Epic and story files
│ └── sprint-status.yaml # Sprint tracking
│ ├── planning-artifacts/
│ │ ├── PRD.md # Your requirements document
│ │ ├── architecture.md # Technical decisions
│ │ └── epics/ # Epic and story files
│ ├── implementation-artifacts/
│ │ └── sprint-status.yaml # Sprint tracking
│ └── project-context.md # Implementation rules (optional)
└── ...
```
@ -171,6 +180,7 @@ your-project/
| `help` | `/bmad-help` | Any | Get guidance on what to do next |
| `prd` | `/bmad-bmm-create-prd` | PM | Create Product Requirements Document |
| `create-architecture` | `/bmad-bmm-create-architecture` | Architect | Create architecture document |
| `generate-project-context` | `/bmad-bmm-generate-project-context` | Analyst | Create project context file |
| `create-epics-and-stories` | `/bmad-bmm-create-epics-and-stories` | PM | Break down PRD into epics |
| `check-implementation-readiness` | `/bmad-bmm-check-implementation-readiness` | Architect | Validate planning cohesion |
| `sprint-planning` | `/bmad-bmm-sprint-planning` | SM | Initialize sprint tracking |

View File

@ -0,0 +1,145 @@
/**
* Tests for CodexSetup.transformToSkillFormat
*
* Validates that descriptions round-trip correctly through parse/stringify,
* producing valid YAML regardless of input quoting style.
*
* Usage: node test/test-codex-transform.js
*/
const path = require('node:path');
const yaml = require('yaml');
// ANSI colors
const colors = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
let passed = 0;
let failed = 0;
function assert(condition, testName, detail) {
if (condition) {
console.log(` ${colors.green}PASS${colors.reset} ${testName}`);
passed++;
} else {
console.log(` ${colors.red}FAIL${colors.reset} ${testName}`);
if (detail) console.log(` ${colors.dim}${detail}${colors.reset}`);
failed++;
}
}
/**
* Parse the output frontmatter and return the description value.
* Validates the output is well-formed YAML that parses back correctly.
*/
function parseOutputDescription(output) {
const match = output.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return null;
const parsed = yaml.parse(match[1]);
return parsed?.description;
}
// Import the class under test
const { CodexSetup } = require(path.join(__dirname, '..', 'tools', 'cli', 'installers', 'lib', 'ide', 'codex.js'));
const setup = new CodexSetup();
console.log(`\n${colors.cyan}CodexSetup.transformToSkillFormat tests${colors.reset}\n`);
// --- Simple description, no quotes ---
{
const input = `---\ndescription: A simple description\n---\n\nBody content here.`;
const result = setup.transformToSkillFormat(input, 'my-skill');
const desc = parseOutputDescription(result);
assert(desc === 'A simple description', 'simple description round-trips', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('\nBody content here.'), 'body preserved for simple description');
}
// --- Description with embedded single quotes (from double-quoted YAML input) ---
{
const input = `---\ndescription: "can't stop won't stop"\n---\n\nBody content here.`;
const result = setup.transformToSkillFormat(input, 'my-skill');
const desc = parseOutputDescription(result);
assert(desc === "can't stop won't stop", 'description with apostrophes round-trips', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('\nBody content here.'), 'body preserved for quoted description');
}
// --- Description with embedded single quote ---
{
const input = `---\ndescription: "it's a test"\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'test-skill');
const desc = parseOutputDescription(result);
assert(desc === "it's a test", 'description with apostrophe round-trips', `got description: ${JSON.stringify(desc)}`);
}
// --- Single-quoted input with pre-escaped apostrophe (YAML '' escape) ---
{
const input = `---\ndescription: 'don''t panic'\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'test-skill');
const desc = parseOutputDescription(result);
assert(desc === "don't panic", 'single-quoted escaped apostrophe round-trips', `got description: ${JSON.stringify(desc)}`);
}
// --- Verify name is set correctly ---
{
const input = `---\ndescription: test\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'my-custom-skill');
const match = result.match(/^---\n([\s\S]*?)\n---/);
const parsed = yaml.parse(match[1]);
assert(parsed.name === 'my-custom-skill', 'name field matches skillName argument', `got name: ${JSON.stringify(parsed.name)}`);
}
// --- Extra frontmatter keys are stripped ---
{
const input = `---\ndescription: foo\ndisable-model-invocation: true\ncustom-field: bar\n---\n\nBody.`;
const result = setup.transformToSkillFormat(input, 'strip-extra');
const desc = parseOutputDescription(result);
assert(desc === 'foo', 'description preserved when extra keys present', `got description: ${JSON.stringify(desc)}`);
const match = result.match(/^---\n([\s\S]*?)\n---/);
const parsed = yaml.parse(match[1]);
assert(parsed.name === 'strip-extra', 'name equals skillName after stripping extras', `got name: ${JSON.stringify(parsed.name)}`);
assert(!('disable-model-invocation' in parsed), 'disable-model-invocation stripped', `keys: ${Object.keys(parsed).join(', ')}`);
assert(!('custom-field' in parsed), 'custom-field stripped', `keys: ${Object.keys(parsed).join(', ')}`);
const keys = Object.keys(parsed).sort();
assert(
keys.length === 2 && keys[0] === 'description' && keys[1] === 'name',
'only name and description remain',
`keys: ${keys.join(', ')}`,
);
}
// --- No frontmatter wraps content ---
{
const input = 'Just some content without frontmatter.';
const result = setup.transformToSkillFormat(input, 'bare-skill');
const desc = parseOutputDescription(result);
assert(desc === 'bare-skill', 'no-frontmatter fallback uses skillName as description', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('Just some content without frontmatter.'), 'body preserved when no frontmatter');
}
// --- No frontmatter with single-quote in skillName ---
{
const input = 'Body content for the skill.';
const result = setup.transformToSkillFormat(input, "it's-a-task");
const desc = parseOutputDescription(result);
assert(desc === "it's-a-task", 'no-frontmatter skillName with single quote round-trips', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('Body content for the skill.'), 'body preserved for single-quote skillName');
}
// --- CRLF frontmatter is parsed correctly (Windows line endings) ---
{
const input = '---\r\ndescription: windows line endings\r\n---\r\n\r\nBody.';
const result = setup.transformToSkillFormat(input, 'crlf-skill');
const desc = parseOutputDescription(result);
assert(desc === 'windows line endings', 'CRLF frontmatter parses correctly', `got description: ${JSON.stringify(desc)}`);
assert(result.includes('Body.'), 'body preserved for CRLF input');
}
// --- Summary ---
console.log(`\n${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);

View File

@ -0,0 +1,216 @@
/**
* Tests for CodexSetup.writeSkillArtifacts
*
* Validates directory creation, SKILL.md file writing, type filtering,
* and integration with transformToSkillFormat.
*
* Usage: node test/test-codex-write-skills.js
*/
const path = require('node:path');
const fs = require('fs-extra');
const os = require('node:os');
const yaml = require('yaml');
// ANSI colors
const colors = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
let passed = 0;
let failed = 0;
function assert(condition, testName, detail) {
if (condition) {
console.log(` ${colors.green}PASS${colors.reset} ${testName}`);
passed++;
} else {
console.log(` ${colors.red}FAIL${colors.reset} ${testName}`);
if (detail) console.log(` ${colors.dim}${detail}${colors.reset}`);
failed++;
}
}
// Import the class under test
const { CodexSetup } = require(path.join(__dirname, '..', 'tools', 'cli', 'installers', 'lib', 'ide', 'codex.js'));
const setup = new CodexSetup();
// Create a temp directory for each test run
let tmpDir;
async function createTmpDir() {
tmpDir = path.join(os.tmpdir(), `bmad-test-skills-${Date.now()}`);
await fs.ensureDir(tmpDir);
return tmpDir;
}
async function cleanTmpDir() {
if (tmpDir) {
await fs.remove(tmpDir);
}
}
async function runTests() {
console.log(`\n${colors.cyan}CodexSetup.writeSkillArtifacts tests${colors.reset}\n`);
// --- Writes a single artifact as a skill directory with SKILL.md ---
{
const destDir = await createTmpDir();
const artifacts = [
{
type: 'task',
relativePath: 'bmm/tasks/create-story.md',
content: '---\ndescription: Create a user story\n---\n\nStory creation instructions.',
},
];
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
assert(count === 1, 'single artifact returns count 1');
const skillDir = path.join(destDir, 'bmad-bmm-create-story');
assert(await fs.pathExists(skillDir), 'skill directory created');
const skillFile = path.join(skillDir, 'SKILL.md');
assert(await fs.pathExists(skillFile), 'SKILL.md file created');
const content = await fs.readFile(skillFile, 'utf8');
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
assert(fmMatch !== null, 'SKILL.md has frontmatter');
const parsed = yaml.parse(fmMatch[1]);
assert(parsed.name === 'bmad-bmm-create-story', 'name matches skill directory name', `got: ${parsed.name}`);
assert(parsed.description === 'Create a user story', 'description preserved', `got: ${parsed.description}`);
assert(content.includes('Story creation instructions.'), 'body content preserved');
await cleanTmpDir();
}
// --- Filters artifacts by type ---
{
const destDir = await createTmpDir();
const artifacts = [
{
type: 'task',
relativePath: 'bmm/tasks/create-story.md',
content: '---\ndescription: A task\n---\n\nTask body.',
},
{
type: 'workflow-command',
relativePath: 'bmm/workflows/plan-project.md',
content: '---\ndescription: A workflow\n---\n\nWorkflow body.',
},
{
type: 'agent-launcher',
relativePath: 'bmm/agents/pm.md',
content: '---\ndescription: An agent\n---\n\nAgent body.',
},
];
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
assert(count === 1, 'only matching type is written when filtering for task');
const entries = await fs.readdir(destDir);
assert(entries.length === 1, 'only one skill directory created', `got ${entries.length}: ${entries.join(', ')}`);
assert(entries[0] === 'bmad-bmm-create-story', 'correct artifact was written', `got: ${entries[0]}`);
await cleanTmpDir();
}
// --- Writes multiple artifacts of the same type ---
{
const destDir = await createTmpDir();
const artifacts = [
{
type: 'workflow-command',
relativePath: 'bmm/workflows/plan-project.md',
content: '---\ndescription: Plan\n---\n\nPlan body.',
},
{
type: 'workflow-command',
relativePath: 'core/workflows/review.md',
content: '---\ndescription: Review\n---\n\nReview body.',
},
];
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'workflow-command');
assert(count === 2, 'two artifacts written');
const entries = new Set((await fs.readdir(destDir)).sort());
assert(entries.has('bmad-bmm-plan-project'), 'first skill directory exists');
assert(entries.has('bmad-review'), 'second skill directory exists (core module)');
await cleanTmpDir();
}
// --- Returns 0 when no artifacts match type ---
{
const destDir = await createTmpDir();
const artifacts = [
{
type: 'agent-launcher',
relativePath: 'bmm/agents/pm.md',
content: '---\ndescription: An agent\n---\n\nBody.',
},
];
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
assert(count === 0, 'returns 0 when no types match');
const entries = await fs.readdir(destDir);
assert(entries.length === 0, 'no directories created when no types match');
await cleanTmpDir();
}
// --- Handles empty artifacts array ---
{
const destDir = await createTmpDir();
const count = await setup.writeSkillArtifacts(destDir, [], 'task');
assert(count === 0, 'returns 0 for empty artifacts array');
await cleanTmpDir();
}
// --- Artifacts without type field are always written ---
{
const destDir = await createTmpDir();
const artifacts = [
{
relativePath: 'bmm/tasks/no-type.md',
content: '---\ndescription: No type field\n---\n\nBody.',
},
];
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
assert(count === 1, 'artifact without type field is written (no filtering)');
await cleanTmpDir();
}
// --- Content without frontmatter gets minimal frontmatter added ---
{
const destDir = await createTmpDir();
const artifacts = [
{
type: 'task',
relativePath: 'bmm/tasks/bare.md',
content: 'Just plain content, no frontmatter.',
},
];
const count = await setup.writeSkillArtifacts(destDir, artifacts, 'task');
assert(count === 1, 'bare content artifact written');
const skillFile = path.join(destDir, 'bmad-bmm-bare', 'SKILL.md');
const content = await fs.readFile(skillFile, 'utf8');
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
assert(fmMatch !== null, 'frontmatter added to bare content');
const parsed = yaml.parse(fmMatch[1]);
assert(parsed.name === 'bmad-bmm-bare', 'name set for bare content', `got: ${parsed.name}`);
assert(content.includes('Just plain content, no frontmatter.'), 'original content preserved');
await cleanTmpDir();
}
// --- Summary ---
console.log(`\n${passed} passed, ${failed} failed\n`);
process.exit(failed > 0 ? 1 : 0);
}
runTests().catch((error) => {
console.error('Test runner error:', error);
process.exit(1);
});

View File

@ -7,10 +7,13 @@ const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
const { toDashPath, customAgentDashName } = require('./shared/path-utils');
const yaml = require('yaml');
const prompts = require('../../../lib/prompts');
/**
* Codex setup handler (CLI mode)
* Writes BMAD artifacts as Agent Skills (agentskills.io format)
* into .agents/skills/ directories.
*/
class CodexSetup extends BaseIdeSetup {
constructor() {
@ -23,35 +26,35 @@ class CodexSetup extends BaseIdeSetup {
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
// Non-interactive mode: use default (global)
// Non-interactive mode: use default (project)
if (options.skipPrompts) {
return { installLocation: 'global' };
return { installLocation: 'project' };
}
let confirmed = false;
let installLocation = 'global';
let installLocation = 'project';
while (!confirmed) {
installLocation = await prompts.select({
message: 'Where would you like to install Codex CLI prompts?',
message: 'Where would you like to install Codex CLI skills?',
choices: [
{
name: 'Global - Simple for single project ' + '(~/.codex/prompts, but references THIS project only)',
value: 'global',
},
{
name: `Project-specific - Recommended for real work (requires CODEX_HOME=<project-dir>${path.sep}.codex)`,
name: 'Project-specific - Recommended (<project>/.agents/skills)',
value: 'project',
},
{
name: 'Global - ($HOME/.agents/skills)',
value: 'global',
},
],
default: 'global',
default: 'project',
});
// Show brief confirmation hint (detailed instructions available via verbose)
if (installLocation === 'project') {
await prompts.log.info('Prompts installed to: <project>/.codex/prompts (requires CODEX_HOME)');
await prompts.log.info('Skills installed to: <project>/.agents/skills');
} else {
await prompts.log.info('Prompts installed to: ~/.codex/prompts');
await prompts.log.info('Skills installed to: $HOME/.agents/skills');
}
// Confirm the choice
@ -80,20 +83,21 @@ class CodexSetup extends BaseIdeSetup {
// Always use CLI mode
const mode = 'cli';
// Get installation location from pre-collected config or default to global
const installLocation = options.preCollectedConfig?.installLocation || 'global';
// Get installation location from pre-collected config or default to project
const installLocation = options.preCollectedConfig?.installLocation || 'project';
const { artifacts, counts } = await this.collectClaudeArtifacts(projectDir, bmadDir, options);
const destDir = this.getCodexPromptDir(projectDir, installLocation);
const destDir = this.getCodexSkillsDir(projectDir, installLocation);
await fs.ensureDir(destDir);
await this.clearOldBmadFiles(destDir, options);
await this.clearOldBmadSkills(destDir, options);
// Collect artifacts and write using underscore format
// Collect and write agent skills
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
const agentCount = await agentGen.writeDashArtifacts(destDir, agentArtifacts);
const agentCount = await this.writeSkillArtifacts(destDir, agentArtifacts, 'agent-launcher');
// Collect and write task skills
const tasks = await getTasksFromBmad(bmadDir, options.selectedModules || []);
const taskArtifacts = [];
for (const task of tasks) {
@ -117,19 +121,23 @@ class CodexSetup extends BaseIdeSetup {
});
}
const ttGen = new TaskToolCommandGenerator(this.bmadFolderName);
const taskSkillArtifacts = taskArtifacts.map((artifact) => ({
...artifact,
content: ttGen.generateCommandContent(artifact, artifact.type),
}));
const tasksWritten = await this.writeSkillArtifacts(destDir, taskSkillArtifacts, 'task');
// Collect and write workflow skills
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
const workflowCount = await workflowGenerator.writeDashArtifacts(destDir, workflowArtifacts);
// Also write tasks using underscore format
const ttGen = new TaskToolCommandGenerator(this.bmadFolderName);
const tasksWritten = await ttGen.writeDashArtifacts(destDir, taskArtifacts);
const workflowCount = await this.writeSkillArtifacts(destDir, workflowArtifacts, 'workflow-command');
const written = agentCount + workflowCount + tasksWritten;
if (!options.silent) {
await prompts.log.success(
`${this.name} configured: ${counts.agents} agents, ${counts.workflows} workflows, ${counts.tasks} tasks, ${written} files → ${destDir}`,
`${this.name} configured: ${counts.agents} agents, ${counts.workflows} workflows, ${counts.tasks} tasks, ${written} skills → ${destDir}`,
);
}
@ -145,13 +153,82 @@ class CodexSetup extends BaseIdeSetup {
}
/**
* Detect Codex installation by checking for BMAD prompt exports
* Write artifacts as Agent Skills (agentskills.io format).
* Each artifact becomes a directory containing SKILL.md.
* @param {string} destDir - Base skills directory
* @param {Array} artifacts - Artifacts to write
* @param {string} artifactType - Type filter (e.g., 'agent-launcher', 'workflow-command', 'task')
* @returns {number} Number of skills written
*/
async writeSkillArtifacts(destDir, artifacts, artifactType) {
let writtenCount = 0;
for (const artifact of artifacts) {
// Filter by type if the artifact has a type field
if (artifact.type && artifact.type !== artifactType) {
continue;
}
// Get the dash-format name (e.g., bmad-bmm-create-prd.md) and remove .md
const flatName = toDashPath(artifact.relativePath);
const skillName = flatName.replace(/\.md$/, '');
// Create skill directory
const skillDir = path.join(destDir, skillName);
await fs.ensureDir(skillDir);
// Transform content: rewrite frontmatter for skills format
const skillContent = this.transformToSkillFormat(artifact.content, skillName);
// Write SKILL.md
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
writtenCount++;
}
return writtenCount;
}
/**
* Transform artifact content from Codex prompt format to Agent Skills format.
* Removes disable-model-invocation, ensures name matches directory.
* @param {string} content - Original content with YAML frontmatter
* @param {string} skillName - Skill name (must match directory name)
* @returns {string} Transformed content
*/
transformToSkillFormat(content, skillName) {
// Parse frontmatter
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
if (!fmMatch) {
// No frontmatter -- wrap with minimal frontmatter
const fm = yaml.stringify({ name: skillName, description: skillName }).trimEnd();
return `---\n${fm}\n---\n\n${content}`;
}
const frontmatter = fmMatch[1];
const body = fmMatch[2];
// Parse frontmatter with yaml library to handle all quoting variants
let description;
try {
const parsed = yaml.parse(frontmatter);
description = parsed?.description || `${skillName} skill`;
} catch {
description = `${skillName} skill`;
}
// Build new frontmatter with only skills-spec fields, let yaml handle quoting
const newFrontmatter = yaml.stringify({ name: skillName, description }).trimEnd();
return `---\n${newFrontmatter}\n---\n${body}`;
}
/**
* Detect Codex installation by checking for BMAD skills
*/
async detect(projectDir) {
// Check both global and project-specific locations
const globalDir = this.getCodexPromptDir(null, 'global');
const globalDir = this.getCodexSkillsDir(null, 'global');
const projectDir_local = projectDir || process.cwd();
const projectSpecificDir = this.getCodexPromptDir(projectDir_local, 'project');
const projectSpecificDir = this.getCodexSkillsDir(projectDir_local, 'project');
// Check global location
if (await fs.pathExists(globalDir)) {
@ -240,27 +317,20 @@ class CodexSetup extends BaseIdeSetup {
};
}
getCodexPromptDir(projectDir = null, location = 'global') {
getCodexSkillsDir(projectDir = null, location = 'project') {
if (location === 'project' && projectDir) {
return path.join(projectDir, '.codex', 'prompts');
return path.join(projectDir, '.agents', 'skills');
}
return path.join(os.homedir(), '.codex', 'prompts');
if (location === 'project' && !projectDir) {
throw new Error('projectDir is required for project-scoped skill installation');
}
return path.join(os.homedir(), '.agents', 'skills');
}
async flattenAndWriteArtifacts(artifacts, destDir) {
let written = 0;
for (const artifact of artifacts) {
const flattenedName = this.flattenFilename(artifact.relativePath);
const targetPath = path.join(destDir, flattenedName);
await fs.writeFile(targetPath, artifact.content);
written++;
}
return written;
}
async clearOldBmadFiles(destDir, options = {}) {
/**
* Remove existing BMAD skill directories from the skills directory.
*/
async clearOldBmadSkills(destDir, options = {}) {
if (!(await fs.pathExists(destDir))) {
return;
}
@ -299,7 +369,8 @@ class CodexSetup extends BaseIdeSetup {
}
async readAndProcessWithProject(filePath, metadata, projectDir) {
const content = await fs.readFile(filePath, 'utf8');
const rawContent = await fs.readFile(filePath, 'utf8');
const content = rawContent.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
return super.processContent(content, metadata, projectDir);
}
@ -311,14 +382,14 @@ class CodexSetup extends BaseIdeSetup {
const lines = [
'IMPORTANT: Codex Configuration',
'',
'/prompts installed globally to your HOME DIRECTORY.',
'Skills installed globally to your HOME DIRECTORY ($HOME/.agents/skills).',
'',
'These prompts reference a specific _bmad path.',
'These skills reference a specific _bmad path.',
"To use with other projects, you'd need to copy the _bmad dir.",
'',
'You can now use /commands in Codex CLI',
' Example: /bmad_bmm_pm',
' Type / to see all available commands',
'Skills are available in Codex CLI automatically.',
' Use /skills to see available skills',
' Skills can also be invoked implicitly based on task description',
];
return lines.join('\n');
}
@ -330,40 +401,15 @@ class CodexSetup extends BaseIdeSetup {
* @returns {string} Instructions text
*/
getProjectSpecificInstructions(projectDir = null, destDir = null) {
const isWindows = os.platform() === 'win32';
const commonLines = [
const lines = [
'Project-Specific Codex Configuration',
'',
`Prompts will be installed to: ${destDir || '<project>/.codex/prompts'}`,
'',
'REQUIRED: You must set CODEX_HOME to use these prompts',
`Skills installed to: ${destDir || '<project>/.agents/skills'}`,
'',
'Codex automatically discovers skills in .agents/skills/ at and above the current directory and in your home directory.',
'No additional configuration is needed.',
];
const windowsLines = [
'Create a codex.cmd file in your project root:',
'',
' @echo off',
' set CODEX_HOME=%~dp0.codex',
' codex %*',
'',
String.raw`Then run: .\codex instead of codex`,
'(The %~dp0 gets the directory of the .cmd file)',
];
const unixLines = [
'Add this alias to your ~/.bashrc or ~/.zshrc:',
'',
' alias codex=\'CODEX_HOME="$PWD/.codex" codex\'',
'',
'After adding, run: source ~/.bashrc (or source ~/.zshrc)',
'(The $PWD uses your current working directory)',
];
const closingLines = ['', 'This tells Codex CLI to use prompts from this project instead of ~/.codex'];
const lines = [...commonLines, ...(isWindows ? windowsLines : unixLines), ...closingLines];
return lines.join('\n');
}
@ -372,31 +418,34 @@ class CodexSetup extends BaseIdeSetup {
*/
async cleanup(projectDir = null) {
// Clean both global and project-specific locations
const globalDir = this.getCodexPromptDir(null, 'global');
await this.clearOldBmadFiles(globalDir);
const globalDir = this.getCodexSkillsDir(null, 'global');
await this.clearOldBmadSkills(globalDir);
if (projectDir) {
const projectSpecificDir = this.getCodexPromptDir(projectDir, 'project');
await this.clearOldBmadFiles(projectSpecificDir);
const projectSpecificDir = this.getCodexSkillsDir(projectDir, 'project');
await this.clearOldBmadSkills(projectSpecificDir);
}
}
/**
* Install a custom agent launcher for Codex
* @param {string} projectDir - Project directory (not used, Codex installs to home)
* Install a custom agent launcher for Codex as an Agent Skill
* @param {string} projectDir - Project directory
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
* @param {string} agentPath - Path to compiled agent (relative to project root)
* @param {Object} metadata - Agent metadata
* @returns {Object|null} Info about created command
* @returns {Object|null} Info about created skill
*/
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
const destDir = this.getCodexPromptDir(projectDir, 'project');
await fs.ensureDir(destDir);
const destDir = this.getCodexSkillsDir(projectDir, 'project');
const launcherContent = `---
name: '${agentName}'
description: '${agentName} agent'
disable-model-invocation: true
// Skill name from the dash name (without .md)
const skillName = customAgentDashName(agentName).replace(/\.md$/, '');
const skillDir = path.join(destDir, skillName);
await fs.ensureDir(skillDir);
const fm = yaml.stringify({ name: skillName, description: `${agentName} agent` }).trimEnd();
const skillContent = `---
${fm}
---
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
@ -411,14 +460,12 @@ You must fully embody this agent's persona and follow all activation instruction
</agent-activation>
`;
// Use underscore format: bmad_custom_fred-commit-poet.md
const fileName = customAgentDashName(agentName);
const launcherPath = path.join(destDir, fileName);
await fs.writeFile(launcherPath, launcherContent, 'utf8');
const skillPath = path.join(skillDir, 'SKILL.md');
await fs.writeFile(skillPath, skillContent, 'utf8');
return {
path: path.relative(projectDir, launcherPath),
command: `/${fileName.replace('.md', '')}`,
path: path.relative(projectDir, skillPath),
command: `$${skillName}`,
};
}
}