fix(installer): generate slash-command and Agent pointer files (OpenCode + GitHub Copilot)#2324
Conversation
|
@coderabbitai review |
🤖 Augment PR SummarySummary: Adds OpenCode direct Changes:
Technical Notes: Descriptions are normalized to single-line YAML-safe values for frontmatter emission. 🤖 Was this summary useful? React with 👍 or 👎 |
📝 WalkthroughWalkthroughThis PR introduces IDE slash-command support by adding command-pointer file generation during installation. The installer now parses the skill manifest, generates per-skill markdown files with YAML frontmatter routed to skill references, manages command artifacts through a dedicated directory, and handles cleanup during uninstallation with test coverage for idempotency. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
test/test-installation-components.js (1)
308-324: Consider extending coverage to cleanup symmetry and hand-edit preservation.The new assertions verify creation and basic idempotency, but two behaviors documented in the PR objectives aren't exercised:
- Hand-edit preservation: The installer skips existing pointer files unless
forceCommandsis set. The current "second install pass" test only re-runs setup with the same generated content, so it can't distinguish "skip-existing" from "rewrite-with-identical-bytes". Pre-write a sentinel byte sequence intocommandFileafter the first install and assert it survives the second run.- Cleanup symmetry:
cleanupCommandPointersremoves pointer files for canonicalIds inremovalSetand deletes the directory when empty. There's no test asserting that a follow-up uninstall (or a removals.txt-driven removal) actually deletes.opencode/commands/bmad-master.mdand the directory.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/test-installation-components.js` around lines 308 - 324, Add tests to verify hand-edit preservation and cleanup symmetry by (1) after the first ideManager.setup('opencode', ...) write a sentinel byte sequence into the commandFile ('.opencode/commands/bmad-master.md'), run ideManager.setup again with the same args (without forceCommands) and assert the file contents still contain the sentinel to prove skip-existing behavior, and (2) exercise cleanupCommandPointers (or call ideManager.setup/remove flow that triggers removalSet) for the bmad canonicalId and assert that '.opencode/commands/bmad-master.md' is removed and the '.opencode/commands' directory is deleted when empty; reference ideManager.setup, forceCommands, and cleanupCommandPointers when locating code to change.tools/installer/ide/_config-driven.js (3)
458-501: Cleanup logic is sound; one minor defensive check is unnecessary.The symmetric pointer-cleanup path (skip when empty
removalSet, only remove.mdentries whose stem is in the set, drop the directory if empty) is correct. The early bail at Line 469 correctly preserves user-authored commands during partial uninstalls becauseremovalSetis built fromskill-manifest.csv+removals.txt, and the directory teardown at Line 494–497 only fires when nothing else lives there.Minor:
typeof entry !== 'string'at Line 482 is dead defensive code —fs.readdir(withoutwithFileTypes) always yields strings. Safe to drop.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/installer/ide/_config-driven.js` around lines 458 - 501, In cleanupCommandPointers, remove the unnecessary defensive check for typeof entry !== 'string' since fs.readdir (called without withFileTypes) always returns strings; locate the loop that iterates over entries (for (const entry of entries)) and delete the condition that guards on typeof entry, keeping only the check that entry.endsWith('.md') and the subsequent canonicalId/removalSet logic so behavior and error handling remain unchanged.
9-31: Reserved-command list is OpenCode-specific but applied to any platform that definescommands_target_dir.
installCommandPointersconsultsRESERVED_OPENCODE_COMMANDSunconditionally for every platform that hascommands_target_dirconfigured. Today that's only OpenCode, so this is harmless. The moment a second IDE opts into command pointers, it will silently inherit OpenCode's reserved-name set — which may neither include its own reserved names nor be appropriate for it.Two cheap mitigations:
- Move the reserved set into
platform-codes.yaml(e.g.installer.reserved_commands: [...]) and let the installer read it per platform; or- Gate the lookup with
if (this.name === 'opencode')and rename the constant accordingly so the coupling is explicit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/installer/ide/_config-driven.js` around lines 9 - 31, The reserved-command list RESERVED_OPENCODE_COMMANDS is being applied globally when installCommandPointers checks commands_target_dir, so move the platform-specific reservation so other platforms don't inherit OpenCode names: either (A) load a per-platform list from configuration (e.g., installer.reserved_commands in platform-codes.yaml) and use that list inside installCommandPointers for the current platform, or (B) restrict the existing lookup to OpenCode only by gating it with a check like this.name === 'opencode' and rename the constant (e.g., RESERVED_OPENCODE_COMMANDS) to make the coupling explicit; update installCommandPointers to retrieve the appropriate reserved set from config or from the platform check before skipping skill name collisions.
168-170:results.commandsis populated but never surfaced.
printSummary(Line 306) only logsskillDirectories || skills, so the newresults.commandsobject — which carries useful counters (created,skippedExisting,skippedCollision,fallbackDescription) — is collected and discarded. For OpenCode installs, users won't know how many slash-command pointers were generated or whether some were skipped due to collisions/existing files.Consider adding a one-liner to
printSummarywhenresults.commands?.created > 0, e.g.${created} commands → ${commands_target_dir}(and a hint whenskippedCollision > 0).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/installer/ide/_config-driven.js` around lines 168 - 170, printSummary currently only logs skillDirectories/skills and discards results.commands returned from installCommandPointers, so users never see created/skipped counts for slash-command pointers; update printSummary to check results.commands (from installCommandPointers) and, when results.commands?.created > 0, append a short line like "<created> commands → <commands_target_dir>" and when results.commands?.skippedCollision > 0 include a hint about collisions; reference the results.commands object fields (created, skippedExisting, skippedCollision, fallbackDescription), and use config.commands_target_dir to display the target directory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tools/installer/ide/_config-driven.js`:
- Around line 195-236: installCommandPointers currently lets a single
fs.writeFile error abort the whole install; wrap the per-record write (the await
fs.writeFile(commandFile, body, 'utf8') inside installCommandPointers) in a
try/catch, add a counter on the result object (e.g. result.pointerWriteFailures
or result.writeFailures) and increment it on failure, and in the catch log a
warning with the canonicalId, commandFile and error (console.warn or the module
logger) instead of rethrowing; also ensure callers (installToTarget) can surface
this summary if needed and apply the same per-file try/catch pattern to
installVerbatimSkills for symmetry.
- Around line 216-232: Compute the pointer file content (body) up front using
description and yamlSafeSingleLine, then if commandFile exists and
!options.forceCommands read its content; if the existing content exactly equals
body, treat as unchanged (increment result.skippedExisting and continue), but if
it matches the generator pattern (e.g., has the frontmatter and the trailing
line `@skills/${canonicalId}\n`) then overwrite with the new body to propagate
description changes (increment result.created or result.fallbackDescription as
appropriate); otherwise (user-modified file differs and does not match the
generator pattern) leave it alone and increment result.skippedExisting. Use the
variables canonicalId, commandFile, options.forceCommands, description, body,
yamlSafeSingleLine, result.skippedExisting, result.created, and
result.fallbackDescription to locate and implement this logic.
---
Nitpick comments:
In `@test/test-installation-components.js`:
- Around line 308-324: Add tests to verify hand-edit preservation and cleanup
symmetry by (1) after the first ideManager.setup('opencode', ...) write a
sentinel byte sequence into the commandFile
('.opencode/commands/bmad-master.md'), run ideManager.setup again with the same
args (without forceCommands) and assert the file contents still contain the
sentinel to prove skip-existing behavior, and (2) exercise
cleanupCommandPointers (or call ideManager.setup/remove flow that triggers
removalSet) for the bmad canonicalId and assert that
'.opencode/commands/bmad-master.md' is removed and the '.opencode/commands'
directory is deleted when empty; reference ideManager.setup, forceCommands, and
cleanupCommandPointers when locating code to change.
In `@tools/installer/ide/_config-driven.js`:
- Around line 458-501: In cleanupCommandPointers, remove the unnecessary
defensive check for typeof entry !== 'string' since fs.readdir (called without
withFileTypes) always returns strings; locate the loop that iterates over
entries (for (const entry of entries)) and delete the condition that guards on
typeof entry, keeping only the check that entry.endsWith('.md') and the
subsequent canonicalId/removalSet logic so behavior and error handling remain
unchanged.
- Around line 9-31: The reserved-command list RESERVED_OPENCODE_COMMANDS is
being applied globally when installCommandPointers checks commands_target_dir,
so move the platform-specific reservation so other platforms don't inherit
OpenCode names: either (A) load a per-platform list from configuration (e.g.,
installer.reserved_commands in platform-codes.yaml) and use that list inside
installCommandPointers for the current platform, or (B) restrict the existing
lookup to OpenCode only by gating it with a check like this.name === 'opencode'
and rename the constant (e.g., RESERVED_OPENCODE_COMMANDS) to make the coupling
explicit; update installCommandPointers to retrieve the appropriate reserved set
from config or from the platform check before skipping skill name collisions.
- Around line 168-170: printSummary currently only logs skillDirectories/skills
and discards results.commands returned from installCommandPointers, so users
never see created/skipped counts for slash-command pointers; update printSummary
to check results.commands (from installCommandPointers) and, when
results.commands?.created > 0, append a short line like "<created> commands →
<commands_target_dir>" and when results.commands?.skippedCollision > 0 include a
hint about collisions; reference the results.commands object fields (created,
skippedExisting, skippedCollision, fallbackDescription), and use
config.commands_target_dir to display the target directory.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4d1dd33b-7b8f-4e48-b127-d142300900f2
📒 Files selected for processing (3)
test/test-installation-components.jstools/installer/ide/_config-driven.jstools/installer/ide/platform-codes.yaml
6065a92 to
9d9d576
Compare
Six fixes from CodeRabbit + Augment review on the OpenCode command
pointer generation:
- skipTarget no longer suppresses installCommandPointers in multi-IDE
shared-target_dir batches. Pointers live in a per-IDE directory and
are not deduped across peers, so OpenCode must still generate them
even when a peer (e.g. openhands) won the .agents/skills write race.
- skipTarget no longer suppresses cleanupCommandPointers either, so
partial uninstalls leave no stale pointers when a peer remains.
- canonicalId is validated as a safe basename before being interpolated
into a file path (defense in depth against a malformed manifest entry
writing outside commands_target_dir).
- yamlSafeSingleLine now quotes descriptions starting with `[` or `{`
so YAML doesn't parse them as a sequence/map.
- Per-record fs.writeFile failures are caught and counted (writeFailures)
rather than aborting the whole IDE install — pointer files are a
non-essential adjunct to the skill copy.
- Generator-shaped pointer files are refreshed when the manifest
description changes; hand-modified files (body diverges from the
generator pattern) are still preserved unless forceCommands is set.
Tests: extends Suite 8 with description-update propagation; adds new
Suite 40c covering OpenCode + openhands batches in both orderings plus
partial-IDE uninstall pointer cleanup. 308 tests pass (was 296).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four nitpicks from CodeRabbit's original review that were missed in the first triage pass: - Hand-edited pointers now survive the production install flow. cleanupCommandPointers spares pointers for canonicalIds that are still in the new manifest when called from the install/update flow (signal: options.previousSkillIds is set). Uninstall and partial-IDE removal flows still wipe pointers as before. The previous behavior wiped every pointer in removalSet before installCommandPointers could run, so its skip-if-exists guard never fired and hand edits were lost on every reinstall — contradicting the docstring's preservation claim. - RESERVED_OPENCODE_COMMANDS is now gated on this.name === 'opencode' so future adapters opting into commands_target_dir don't silently inherit OpenCode's reserved-name set. - printSummary now surfaces results.commands so users see how many pointers were created/refreshed/skipped per install, plus a warning for any per-file write failures. - Dropped a dead `typeof entry !== 'string'` check; fs.readdir without withFileTypes always yields strings. Tests: extends Suite 8 with a hand-edit-preservation regression that calls setup with previousSkillIds (the production shape) and asserts a sentinel byte sequence in the pointer body survives. 310 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six fixes from CodeRabbit + Augment review on the OpenCode command
pointer generation:
- skipTarget no longer suppresses installCommandPointers in multi-IDE
shared-target_dir batches. Pointers live in a per-IDE directory and
are not deduped across peers, so OpenCode must still generate them
even when a peer (e.g. openhands) won the .agents/skills write race.
- skipTarget no longer suppresses cleanupCommandPointers either, so
partial uninstalls leave no stale pointers when a peer remains.
- canonicalId is validated as a safe basename before being interpolated
into a file path (defense in depth against a malformed manifest entry
writing outside commands_target_dir).
- yamlSafeSingleLine now quotes descriptions starting with `[` or `{`
so YAML doesn't parse them as a sequence/map.
- Per-record fs.writeFile failures are caught and counted (writeFailures)
rather than aborting the whole IDE install — pointer files are a
non-essential adjunct to the skill copy.
- Generator-shaped pointer files are refreshed when the manifest
description changes; hand-modified files (body diverges from the
generator pattern) are still preserved unless forceCommands is set.
Tests: extends Suite 8 with description-update propagation; adds new
Suite 40c covering OpenCode + openhands batches in both orderings plus
partial-IDE uninstall pointer cleanup. 308 tests pass (was 296).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four nitpicks from CodeRabbit's original review that were missed in the first triage pass: - Hand-edited pointers now survive the production install flow. cleanupCommandPointers spares pointers for canonicalIds that are still in the new manifest when called from the install/update flow (signal: options.previousSkillIds is set). Uninstall and partial-IDE removal flows still wipe pointers as before. The previous behavior wiped every pointer in removalSet before installCommandPointers could run, so its skip-if-exists guard never fired and hand edits were lost on every reinstall — contradicting the docstring's preservation claim. - RESERVED_OPENCODE_COMMANDS is now gated on this.name === 'opencode' so future adapters opting into commands_target_dir don't silently inherit OpenCode's reserved-name set. - printSummary now surfaces results.commands so users see how many pointers were created/refreshed/skipped per install, plus a warning for any per-file write failures. - Dropped a dead `typeof entry !== 'string'` check; fs.readdir without withFileTypes always yields strings. Tests: extends Suite 8 with a hand-edit-preservation regression that calls setup with previousSkillIds (the production shape) and asserts a sentinel byte sequence in the pointer body survives. 310 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8210a82 to
ccc0d4f
Compare
Following up on the 4 CodeRabbit nitpicks from the original review.All addressed in d3bf855 and ccc0d4f:
Test count: 296 → 310. |
Six fixes from CodeRabbit + Augment review on the OpenCode command
pointer generation:
- skipTarget no longer suppresses installCommandPointers in multi-IDE
shared-target_dir batches. Pointers live in a per-IDE directory and
are not deduped across peers, so OpenCode must still generate them
even when a peer (e.g. openhands) won the .agents/skills write race.
- skipTarget no longer suppresses cleanupCommandPointers either, so
partial uninstalls leave no stale pointers when a peer remains.
- canonicalId is validated as a safe basename before being interpolated
into a file path (defense in depth against a malformed manifest entry
writing outside commands_target_dir).
- yamlSafeSingleLine now quotes descriptions starting with `[` or `{`
so YAML doesn't parse them as a sequence/map.
- Per-record fs.writeFile failures are caught and counted (writeFailures)
rather than aborting the whole IDE install — pointer files are a
non-essential adjunct to the skill copy.
- Generator-shaped pointer files are refreshed when the manifest
description changes; hand-modified files (body diverges from the
generator pattern) are still preserved unless forceCommands is set.
Tests: extends Suite 8 with description-update propagation; adds new
Suite 40c covering OpenCode + openhands batches in both orderings plus
partial-IDE uninstall pointer cleanup. 308 tests pass (was 296).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four nitpicks from CodeRabbit's original review that were missed in the first triage pass: - Hand-edited pointers now survive the production install flow. cleanupCommandPointers spares pointers for canonicalIds that are still in the new manifest when called from the install/update flow (signal: options.previousSkillIds is set). Uninstall and partial-IDE removal flows still wipe pointers as before. The previous behavior wiped every pointer in removalSet before installCommandPointers could run, so its skip-if-exists guard never fired and hand edits were lost on every reinstall — contradicting the docstring's preservation claim. - RESERVED_OPENCODE_COMMANDS is now gated on this.name === 'opencode' so future adapters opting into commands_target_dir don't silently inherit OpenCode's reserved-name set. - printSummary now surfaces results.commands so users see how many pointers were created/refreshed/skipped per install, plus a warning for any per-file write failures. - Dropped a dead `typeof entry !== 'string'` check; fs.readdir without withFileTypes always yields strings. Tests: extends Suite 8 with a hand-edit-preservation regression that calls setup with previousSkillIds (the production shape) and asserts a sentinel byte sequence in the pointer body survives. 310 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ccc0d4f to
6bc635f
Compare
Adds .opencode/commands/<canonicalId>.md pointer files for each installed skill so users can invoke skills directly (e.g. /bmad-quick-dev) instead of going through the /skills menu. - platform-codes.yaml: add commands_target_dir field for opencode - _config-driven.js: installCommandPointers() with skip-if-exists default, reserved-name collision guard, YAML-safe description quoting - _config-driven.js: cleanupCommandPointers() for symmetric uninstall - test-installation-components.js: extend OpenCode suite with assertions covering pointer creation, content, and idempotency OpenCode-only and opt-in via the new yaml field; other adapters unchanged. Refs bmad-code-org#2267 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six fixes from CodeRabbit + Augment review on the OpenCode command
pointer generation:
- skipTarget no longer suppresses installCommandPointers in multi-IDE
shared-target_dir batches. Pointers live in a per-IDE directory and
are not deduped across peers, so OpenCode must still generate them
even when a peer (e.g. openhands) won the .agents/skills write race.
- skipTarget no longer suppresses cleanupCommandPointers either, so
partial uninstalls leave no stale pointers when a peer remains.
- canonicalId is validated as a safe basename before being interpolated
into a file path (defense in depth against a malformed manifest entry
writing outside commands_target_dir).
- yamlSafeSingleLine now quotes descriptions starting with `[` or `{`
so YAML doesn't parse them as a sequence/map.
- Per-record fs.writeFile failures are caught and counted (writeFailures)
rather than aborting the whole IDE install — pointer files are a
non-essential adjunct to the skill copy.
- Generator-shaped pointer files are refreshed when the manifest
description changes; hand-modified files (body diverges from the
generator pattern) are still preserved unless forceCommands is set.
Tests: extends Suite 8 with description-update propagation; adds new
Suite 40c covering OpenCode + openhands batches in both orderings plus
partial-IDE uninstall pointer cleanup. 308 tests pass (was 296).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four nitpicks from CodeRabbit's original review that were missed in the first triage pass: - Hand-edited pointers now survive the production install flow. cleanupCommandPointers spares pointers for canonicalIds that are still in the new manifest when called from the install/update flow (signal: options.previousSkillIds is set). Uninstall and partial-IDE removal flows still wipe pointers as before. The previous behavior wiped every pointer in removalSet before installCommandPointers could run, so its skip-if-exists guard never fired and hand edits were lost on every reinstall — contradicting the docstring's preservation claim. - RESERVED_OPENCODE_COMMANDS is now gated on this.name === 'opencode' so future adapters opting into commands_target_dir don't silently inherit OpenCode's reserved-name set. - printSummary now surfaces results.commands so users see how many pointers were created/refreshed/skipped per install, plus a warning for any per-file write failures. - Dropped a dead `typeof entry !== 'string'` check; fs.readdir without withFileTypes always yields strings. Tests: extends Suite 8 with a hand-edit-preservation regression that calls setup with previousSkillIds (the production shape) and asserts a sentinel byte sequence in the pointer body survives. 310 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6bc635f to
46a3d85
Compare
…gents Re-scopes bmad-code-org#2324 to cover the second user-facing pain: GitHub Copilot's Custom Agents picker, where installed BMAD skills currently don't show up even though slash commands work natively. Generalizes the per-platform pointer-file mechanism so the same installCommandPointers / cleanupCommandPointers code path serves both OpenCode (slash commands palette) and Copilot (Custom Agents picker), with all platform-specific shape pushed into platform-codes.yaml as data: - commands_target_dir — where pointer files live (existing) - commands_extension — file extension (default '.md'; Copilot uses '.agent.md' per VS Code Custom Agents docs) - commands_body_template — pointer body, supports {canonicalId} and {target_dir} placeholders. Default matches OpenCode's `@skills/<id>` resolver. Copilot has no such resolver, so its template uses the {project-root}/<target_dir>/<id>/SKILL.md LOAD pattern (consistent with PR bmad-code-org#1769). OpenCode behavior is unchanged. Copilot users now get a per-skill .github/agents/<canonicalId>.agent.md file that surfaces the skill in the Custom Agents picker — addressing the "agents being gone" complaint flagged by enterprise users. Tests: extends Suite 17 with assertions for Copilot agent pointer creation, body content (LOAD pattern with {project-root}-rooted path), and idempotency. 318 tests pass (was 310). Refs bmad-code-org#2267 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… only Earlier commit naively wrote a `.github/agents/<id>.agent.md` for every installed skill, which would clutter the Custom Agents picker with 90+ workflow/tool entries that don't belong there. Adds an `agents-only` filter that gates the per-skill emission on whether the canonical id signals a persona agent: - Primary rule: id contains `-agent-` (e.g. `bmad-agent-pm`, `gds-agent-game-dev`, `wds-agent-freya-ux`, `bmad-cis-agent-storyteller`). - Allowlist: `bmad-tea` — TEA's Murat persona uses the bare module code rather than the `-agent-` convention. Listed explicitly so the rule still surfaces it. Verified against the full installed manifest (114 skills): catches all 20 description-confirmed personas across BMM, CIS, GDS, WDS, TEA; excludes all 94 workflows/tools. Wired through a new yaml field on github-copilot: commands_filter: agents-only OpenCode is unaffected — it has no `commands_filter` set, so the loop behaves as before (every skill becomes a slash command). Tests: extends Suite 17 with a multi-skill manifest fixture covering persona/agent + bmad-tea + workflow cases; asserts persona agents and bmad-tea get .agent.md files while workflows do not. 322 tests pass. Refs bmad-code-org#2267 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
First — thanks for taking this on. Restoring the BMAD personas in Copilot's Custom Agents picker is a big win for the enterprise users who've been asking for it, and the fact that you generalized the mechanism to cover OpenCode in the same code path is exactly the right shape. Appreciate the work. The data-driven The Suggested change: derive "is this a persona agent?" from the presence of Verified across all five modules — every persona has it, no workflow/tool has it:
Total = 20, matches your "20 description-confirmed personas" exactly. Why this is better than a name regex or a new
Implementation sketch. The manifest already records ```js One file read per skill at install time, no manifest column, no frontmatter change. The Everything else in the PR can land as written. Happy to merge once this swap is in. |
Per maintainer review on PR bmad-code-org#2324: the `-agent-` naming convention isn't a load-bearing contract anywhere else in the codebase, and the bmad-tea allowlist already shows it starting to break. A future persona that doesn't follow the convention would silently disappear from the Copilot Custom Agents picker. Replaces the name-based filter with a behavior-based signal: read each skill's source `customize.toml` and check for an `[agent]` section. This is the actual configuration source of truth — every BMAD persona is configured under `[agent]`, every workflow under `[workflow]`, every standalone skill has no customize.toml. Verified on disk against the full installed manifest (114 skills): - 20 personas detected — exactly the description-confirmed count across BMM, CIS, GDS, WDS, TEA. bmad-tea is caught natively (no allowlist). - 94 workflows/tools correctly excluded. - `bmad-agent-builder` (meta-skill that builds agent skills) is now CORRECTLY excluded — its canonical id contains `-agent-` but its customize.toml has [workflow], not [agent], because it isn't a persona itself. The previous naming-based filter was including it in the agents picker, which would have been a silent UX bug. `NON_CONVENTIONAL_AGENT_IDS` constant is removed entirely — the toml signal subsumes it. Tests: extends Suite 17 with a 4-skill fixture that covers persona + non-conventional persona + workflow + meta-skill cases. 388 tests pass. Refs bmad-code-org#2267 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a single, deliberate exception to the toml-based agents-only filter:
`bmad-help` is the structural meta-skill across BMAD — the orientation
helper that points users at every other skill. Users invoke it
persona-style ("ask the helper") even though it has no `[agent]`
customize.toml of its own (it isn't a configurable persona).
Implemented as a one-element ALWAYS_AGENT_IDS set rather than a hardcode
in the function body so the exception is named, documented, and
discoverable. The skill is structurally unique — there is no second
meta-help skill — so this is not the start of a growing allowlist; it's
a one-off for the one orientation surface BMAD ships.
Verified on disk: agents picker now shows 21 entries (20 personas via
[agent] in customize.toml + bmad-help). bmad-agent-builder stays
correctly excluded (its customize.toml has [workflow], not [agent]).
Tests: extends Suite 17 with a `bmad-help` fixture (no customize.toml,
must still appear in agents picker). 389 tests pass.
Refs bmad-code-org#2267
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Agreed — naming convention was a trap. Swapped to the customize.toml signal in Bonus: the new signal correctly excludes
Disk-verified against the full installed manifest (114 skills): 20 personas detected, 94 skipped. Implementation matches your sketch except using One addition worth flagging(commit So Reasoning: Same shape you flagged for
Defaulting to A; happy to swap to B in this PR if you'd prefer. 389 tests pass. |
The Problem
Two pain points from real BMAD users that this PR addresses together because they're the same architectural shape:
OpenCode users:
Can't invoke installed skills directly. Today, every skill use requires:
/skillsto open the OpenCode skills menu.GitHub Copilot users:
Report installed BMAD 'Agents' ( and NOT Workflows or misc tools ) Skills no longer appear in the Github Copilot Custom 'Agents' picker since the consolidation to skills
Both are flagged in the follow-up to #2267.
The Proposed Solution
One generic per-platform pointer-file mechanism in the installer, with all platform-specific shape pushed into
platform-codes.yamlas data — no per-platform branching in the JS..opencode/commands/<canonicalId>.mdwith@skills/<canonicalId>body, populating OpenCode's slash-command palette..github/agents/<canonicalId>.agent.mdfor persona agents only (canonical id contains-agent-, plusbmad-tea), with aLOAD {project-root}/.agents/skills/<id>/SKILL.mdbody. Workflows and tools are filtered out so the Custom Agents picker stays uncluttered.installCommandPointers/cleanupCommandPointersread three new yaml fields and produce the right file shape for each platform.Scope
This PR only modifies the BMAD-METHOD installer. Two platforms opt in via three new fields in
platform-codes.yaml:commands_target_dir— where pointer files live.commands_extension— file extension (default.md; Copilot uses.agent.mdper the VS Code Custom Agents docs).commands_body_template— pointer body, supports{canonicalId}and{target_dir}placeholders.commands_filter— optional, set toagents-onlyto restrict emission to persona agents (BMAD's-agent-naming convention plus thebmad-teaallowlist). Used by github-copilot so the Custom Agents picker isn't flooded with 90+ workflow entries.Other adapters in the installer are unaffected — the new code path is gated behind
commands_target_dirbeing set. Future platforms with the same gap can opt in by adding the same three fields to their entry; no JS changes required.External / expansion modules (WDS, GDS, CIS, TEA)
External-module skills automatically receive pointers too for both OpenCode and Copilot — they flow through the same unified
_bmad/_config/skill-manifest.csvthat this PR'sinstallCommandPointersreads. No parallel code changes are required in the external-module repos:bmad-code-org/bmad-method-wds-expansion(WDS)bmad-code-org/bmad-module-game-dev-studio(GDS)bmad-code-org/bmad-module-creative-intelligence-suite(CIS)bmad-code-org/bmad-method-test-architecture-enterprise(TEA)These repos are content-only (no installer code; the per-module installer pattern was deliberately removed in favor of declarative config — see
official-modules.js:553). After this PR lands, a user who runsbmad installand selects any of these modules gets/<canonicalId>slash commands (OpenCode, all skills) and Custom Agents picker entries (Copilot, persona agents only) generated automatically.Files
tools/installer/ide/platform-codes.yaml— addscommands_target_dirfor opencode and addscommands_target_dir+commands_extension+commands_body_templatefor github-copilot (4 lines total)tools/installer/ide/_config-driven.js— genericinstallCommandPointers()/cleanupCommandPointers()driven by per-platform yaml data, plus reserved-name collision guard, basename validation, YAML-safe description quoting, hand-edit preservation, and per-file try/catchtest/test-installation-components.js— extends Suite 8 (OpenCode) and Suite 17 (GitHub Copilot) with assertions for pointer creation, body content, idempotency, and per-platform shapeBehavior
_bmad/_config/skill-manifest.csv, writes<commands_target_dir>/<canonicalId><extension>with the description in YAML frontmatter and the platform's body template (placeholder-expanded).review,commit,init,help,skills,fast, etc.). Gated onthis.name === 'opencode'so other platforms don't inherit OpenCode-specific reservations..agents/skillsshared-target batch.Tests
Local
npm testgreen: lint, format, markdown-lint, 318 installer-component tests (extended Suite 8 + Suite 17 with new assertions for both platforms; was 290 pre-PR), 83 channel-resolution tests.Why this scope (not broader)
The
Related — other IDEs / LLMssection that earlier versions of this PR included has been removed. Of the 8 platforms originally listed, only OpenCode and GitHub Copilot are hands-on verified and have explicit user demand. The rest were desktop research. If/when other platforms need the same fix, they can opt in with a small yaml-only change — no installer code edits required.Coordination
Happy to close this if a parallel skills/installer rework is in flight that would conflict — let me know.
Refs: #2267