diff --git a/src/core/tasks/bmad-shard-doc-skill-prototype/SKILL.md b/src/core/tasks/bmad-shard-doc-skill-prototype/SKILL.md
new file mode 100644
index 000000000..afa37c831
--- /dev/null
+++ b/src/core/tasks/bmad-shard-doc-skill-prototype/SKILL.md
@@ -0,0 +1,12 @@
+---
+name: bmad-shard-doc-skill-prototype
+description: Prototype native skill wrapper for shard-doc during transition.
+---
+
+# bmad-shard-doc-skill-prototype
+
+Prototype marker: source-authored-skill
+
+Read and execute from: {project-root}/_bmad/core/tasks/shard-doc.xml
+
+Follow all shard-doc task instructions exactly as written.
diff --git a/src/core/tasks/bmad-shard-doc-skill-prototype/bmad-skill-manifest.yaml b/src/core/tasks/bmad-shard-doc-skill-prototype/bmad-skill-manifest.yaml
new file mode 100644
index 000000000..29ce5aeb6
--- /dev/null
+++ b/src/core/tasks/bmad-shard-doc-skill-prototype/bmad-skill-manifest.yaml
@@ -0,0 +1,3 @@
+canonicalId: bmad-shard-doc-skill-prototype
+type: task
+description: "Prototype native skill wrapper for shard-doc during installer transition"
diff --git a/test/test-installation-components.js b/test/test-installation-components.js
index 56f37b365..a2e930aee 100644
--- a/test/test-installation-components.js
+++ b/test/test-installation-components.js
@@ -81,6 +81,61 @@ async function createTestBmadFixture() {
return fixtureDir;
}
+async function createShardDocPrototypeFixture() {
+ const fixtureDir = await createTestBmadFixture();
+
+ await fs.ensureDir(path.join(fixtureDir, 'core', 'tasks'));
+ await fs.writeFile(
+ path.join(fixtureDir, 'core', 'tasks', 'shard-doc.xml'),
+ 'Test objective\n',
+ );
+
+ await fs.writeFile(
+ path.join(fixtureDir, 'core', 'tasks', 'bmad-skill-manifest.yaml'),
+ [
+ 'shard-doc.xml:',
+ ' canonicalId: bmad-shard-doc',
+ ' type: task',
+ ' description: "Splits large markdown documents into smaller, organized files based on sections"',
+ '',
+ ].join('\n'),
+ );
+
+ await fs.ensureDir(path.join(fixtureDir, 'core', 'tasks', 'bmad-shard-doc-skill-prototype'));
+ await fs.writeFile(
+ path.join(fixtureDir, 'core', 'tasks', 'bmad-shard-doc-skill-prototype', 'SKILL.md'),
+ [
+ '---',
+ 'name: bmad-shard-doc-skill-prototype',
+ 'description: Source-authored prototype skill',
+ '---',
+ '',
+ '# bmad-shard-doc-skill-prototype',
+ '',
+ 'Prototype marker: source-authored-skill',
+ '',
+ 'Read and execute from: {project-root}/_bmad/core/tasks/shard-doc.xml',
+ '',
+ 'Follow all shard-doc task instructions exactly as written.',
+ '',
+ ].join('\n'),
+ );
+
+ await fs.writeFile(
+ path.join(fixtureDir, '_config', 'task-manifest.csv'),
+ [
+ 'name,displayName,description,module,path,standalone,canonicalId',
+ 'shard-doc,Shard Document,Test shard-doc task,core,_bmad/core/tasks/shard-doc.xml,true,bmad-shard-doc',
+ '',
+ ].join('\n'),
+ );
+
+ // Ensure tool manifest exists to avoid parser edge-cases in some environments.
+ await fs.writeFile(path.join(fixtureDir, '_config', 'tool-manifest.csv'), '');
+
+ return fixtureDir;
+}
+
/**
* Test Suite
*/
@@ -827,6 +882,70 @@ async function runTests() {
console.log('');
// ============================================================
+ // Test 11: Shard-doc Prototype Duplication (Skill-Format Only)
+ // ============================================================
+ console.log(`${colors.yellow}Test Suite 11: Shard-doc Prototype Duplication${colors.reset}\n`);
+
+ let tempCodexProjectDir;
+ let tempGeminiProjectDir;
+ let installedBmadDir;
+ try {
+ clearCache();
+ const platformCodes = await loadPlatformCodes();
+ const codexInstaller = platformCodes.platforms.codex?.installer;
+ const geminiInstaller = platformCodes.platforms.gemini?.installer;
+
+ assert(codexInstaller?.skill_format === true, 'Codex installer uses skill_format output');
+ assert(geminiInstaller?.skill_format === true, 'Gemini installer uses skill_format output');
+
+ tempCodexProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-prototype-test-'));
+ tempGeminiProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-gemini-prototype-test-'));
+ installedBmadDir = await createShardDocPrototypeFixture();
+
+ const ideManager = new IdeManager();
+ await ideManager.ensureInitialized();
+
+ const codexResult = await ideManager.setup('codex', tempCodexProjectDir, installedBmadDir, {
+ silent: true,
+ selectedModules: ['bmm'],
+ });
+
+ assert(codexResult.success === true, 'Codex setup succeeds for shard-doc prototype fixture');
+
+ const codexCanonicalSkill = path.join(tempCodexProjectDir, '.agents', 'skills', 'bmad-shard-doc', 'SKILL.md');
+ const codexPrototypeSkill = path.join(tempCodexProjectDir, '.agents', 'skills', 'bmad-shard-doc-skill-prototype', 'SKILL.md');
+ assert(await fs.pathExists(codexCanonicalSkill), 'Codex install writes canonical shard-doc skill');
+ assert(await fs.pathExists(codexPrototypeSkill), 'Codex install writes duplicated shard-doc prototype skill');
+
+ const codexCanonicalContent = await fs.readFile(codexCanonicalSkill, 'utf8');
+ const codexPrototypeContent = await fs.readFile(codexPrototypeSkill, 'utf8');
+ assert(codexCanonicalContent.includes('name: bmad-shard-doc'), 'Canonical shard-doc skill keeps canonical frontmatter name');
+ assert(
+ codexPrototypeContent.includes('name: bmad-shard-doc-skill-prototype'),
+ 'Prototype shard-doc skill uses prototype frontmatter name',
+ );
+ assert(
+ codexPrototypeContent.includes('Prototype marker: source-authored-skill'),
+ 'Prototype shard-doc skill is copied from source SKILL.md',
+ );
+
+ const geminiResult = await ideManager.setup('gemini', tempGeminiProjectDir, installedBmadDir, {
+ silent: true,
+ selectedModules: ['bmm'],
+ });
+
+ assert(geminiResult.success === true, 'Gemini setup succeeds for shard-doc prototype fixture');
+
+ const geminiCanonicalSkill = path.join(tempGeminiProjectDir, '.gemini', 'skills', 'bmad-shard-doc', 'SKILL.md');
+ const geminiPrototypeSkill = path.join(tempGeminiProjectDir, '.gemini', 'skills', 'bmad-shard-doc-skill-prototype', 'SKILL.md');
+ assert(await fs.pathExists(geminiCanonicalSkill), 'Gemini install writes canonical shard-doc skill');
+ assert(await fs.pathExists(geminiPrototypeSkill), 'Gemini install writes duplicated shard-doc prototype skill');
+ } catch (error) {
+ assert(false, 'Shard-doc prototype duplication test succeeds', error.message);
+ } finally {
+ await Promise.allSettled([tempCodexProjectDir, tempGeminiProjectDir, installedBmadDir].filter(Boolean).map((dir) => fs.remove(dir)));
+ }
+
// Test 17: GitHub Copilot Native Skills Install
// ============================================================
console.log(`${colors.yellow}Test Suite 17: GitHub Copilot Native Skills${colors.reset}\n`);
diff --git a/tools/cli/installers/lib/ide/_config-driven.js b/tools/cli/installers/lib/ide/_config-driven.js
index 0a311a68d..8595033a0 100644
--- a/tools/cli/installers/lib/ide/_config-driven.js
+++ b/tools/cli/installers/lib/ide/_config-driven.js
@@ -132,21 +132,21 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
if (!artifact_types || artifact_types.includes('agents')) {
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
- results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config);
+ results.agents = await this.writeAgentArtifacts(targetPath, artifacts, template_type, config, bmadDir);
}
// Install workflows
if (!artifact_types || artifact_types.includes('workflows')) {
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
- results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config);
+ results.workflows = await this.writeWorkflowArtifacts(targetPath, artifacts, template_type, config, bmadDir);
}
// Install tasks and tools using template system (supports TOML for Gemini, MD for others)
if (!artifact_types || artifact_types.includes('tasks') || artifact_types.includes('tools')) {
const taskToolGen = new TaskToolCommandGenerator(this.bmadFolderName);
const { artifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
- const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config);
+ const taskToolResult = await this.writeTaskToolArtifacts(targetPath, artifacts, template_type, config, bmadDir);
results.tasks = taskToolResult.tasks || 0;
results.tools = taskToolResult.tools || 0;
}
@@ -187,7 +187,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
* @param {Object} config - Installation configuration
* @returns {Promise} Count of artifacts written
*/
- async writeAgentArtifacts(targetPath, artifacts, templateType, config = {}) {
+ async writeAgentArtifacts(targetPath, artifacts, templateType, config = {}, bmadDir = null) {
// Try to load platform-specific template, fall back to default-agent
const { content: template, extension } = await this.loadTemplate(templateType, 'agent', config, 'default-agent');
let count = 0;
@@ -197,7 +197,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
const filename = this.generateFilename(artifact, 'agent', extension);
if (config.skill_format) {
- await this.writeSkillFile(targetPath, artifact, content);
+ await this.writeSkillFile(targetPath, artifact, content, bmadDir);
} else {
const filePath = path.join(targetPath, filename);
await this.writeFile(filePath, content);
@@ -216,7 +216,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
* @param {Object} config - Installation configuration
* @returns {Promise} Count of artifacts written
*/
- async writeWorkflowArtifacts(targetPath, artifacts, templateType, config = {}) {
+ async writeWorkflowArtifacts(targetPath, artifacts, templateType, config = {}, bmadDir = null) {
let count = 0;
for (const artifact of artifacts) {
@@ -235,7 +235,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
const filename = this.generateFilename(artifact, 'workflow', extension);
if (config.skill_format) {
- await this.writeSkillFile(targetPath, artifact, content);
+ await this.writeSkillFile(targetPath, artifact, content, bmadDir);
} else {
const filePath = path.join(targetPath, filename);
await this.writeFile(filePath, content);
@@ -255,7 +255,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
* @param {Object} config - Installation configuration
* @returns {Promise