From 129f2d4ac9e897c25a5a6da1bf9331c9fc9d98b2 Mon Sep 17 00:00:00 2001 From: Dicky Moore Date: Sun, 8 Feb 2026 14:05:42 +0000 Subject: [PATCH] fix: hide internal workflow task from codex exports --- test/test-installation-components.js | 35 +++++++++++++++++++ .../lib/ide/shared/bmad-artifacts.js | 30 ++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/test/test-installation-components.js b/test/test-installation-components.js index eefa1ed4a..7c456f570 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -21,6 +21,7 @@ const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest const { WorkflowCommandGenerator } = require('../tools/cli/installers/lib/ide/shared/workflow-command-generator'); const { TaskToolCommandGenerator } = require('../tools/cli/installers/lib/ide/shared/task-tool-command-generator'); const { IdeManager } = require('../tools/cli/installers/lib/ide/manager'); +const { CodexSetup } = require('../tools/cli/installers/lib/ide/codex'); const { ModuleManager } = require('../tools/cli/installers/lib/modules/manager'); const { BMAD_FOLDER_NAME } = require('../tools/cli/installers/lib/ide/shared/path-utils'); @@ -707,6 +708,40 @@ internal: true console.log(''); + // ============================================================ + // Test 18: Codex Task Visibility Guard + // ============================================================ + console.log(`${colors.yellow}Test Suite 18: Codex Task Visibility Guard${colors.reset}\n`); + + try { + const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-visibility-')); + const projectDir = path.join(tmpRoot, 'project'); + const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME); + await fs.ensureDir(projectDir); + await fs.copy(path.join(projectRoot, 'src', 'core'), path.join(bmadDir, 'core')); + await fs.copy(path.join(projectRoot, 'src', 'bmm'), path.join(bmadDir, 'bmm')); + + const manifestGenerator = new ManifestGenerator(); + await manifestGenerator.generateManifests(bmadDir, ['bmm'], [], { ides: ['codex'] }); + + const codexSetup = new CodexSetup(); + await codexSetup.setup(projectDir, bmadDir, { + selectedModules: ['bmm'], + preCollectedConfig: { installLocation: 'project' }, + }); + + const promptsDir = path.join(projectDir, '.codex', 'prompts'); + const generated = await fs.readdir(promptsDir); + + assert(!generated.includes('bmad-workflow.md'), 'Codex export excludes internal workflow runner task prompt', generated.join(', ')); + + await fs.remove(tmpRoot); + } catch (error) { + assert(false, 'Codex task visibility guard runs', error.message); + } + + console.log(''); + // ============================================================ // Summary // ============================================================ diff --git a/tools/cli/installers/lib/ide/shared/bmad-artifacts.js b/tools/cli/installers/lib/ide/shared/bmad-artifacts.js index 7bcfd6a79..a506d4f0e 100644 --- a/tools/cli/installers/lib/ide/shared/bmad-artifacts.js +++ b/tools/cli/installers/lib/ide/shared/bmad-artifacts.js @@ -1,5 +1,6 @@ const path = require('node:path'); const fs = require('fs-extra'); +const yaml = require('yaml'); /** * Helpers for gathering BMAD agents/tasks from the installed tree. @@ -149,8 +150,33 @@ async function getTasksFromDir(dirPath, moduleName) { const filePath = path.join(dirPath, file); const content = await fs.readFile(filePath, 'utf8'); - // Skip internal/engine files (not user-facing tasks) - if (content.includes('internal="true"')) { + let isInternal = false; + let isStandalone = true; + + if (file.endsWith('.md')) { + // Parse markdown frontmatter for standalone/internal flags. + const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); + if (frontmatterMatch) { + try { + const frontmatter = yaml.parse(frontmatterMatch[1]) || {}; + isInternal = frontmatter.internal === true || frontmatter.internal === 'true'; + if (frontmatter.standalone === false || frontmatter.standalone === 'false') { + isStandalone = false; + } + } catch { + // Keep defaults when frontmatter parsing fails. + } + } + } else { + // XML tasks rely on attributes for standalone/internal visibility. + isInternal = /internal\s*=\s*["']true["']/i.test(content); + if (/standalone\s*=\s*["']false["']/i.test(content)) { + isStandalone = false; + } + } + + // Skip internal/engine or explicitly non-standalone tasks. + if (isInternal || !isStandalone) { continue; }