fix(dispatch): resolve plugin namespace parsing, template substitution, and discovery duplication
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { parseSlashCommand } from "./detector"
|
||||
|
||||
describe("slash command parsing pattern", () => {
|
||||
describe("#given plugin namespace includes dot", () => {
|
||||
it("#then parses command name with dot and colon", () => {
|
||||
// given
|
||||
const text = "/my.plugin:run ship"
|
||||
|
||||
// when
|
||||
const parsed = parseSlashCommand(text)
|
||||
|
||||
// then
|
||||
expect(parsed).not.toBeNull()
|
||||
expect(parsed?.command).toBe("my.plugin:run")
|
||||
expect(parsed?.args).toBe("ship")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@ export const HOOK_NAME = "auto-slash-command" as const
|
||||
export const AUTO_SLASH_COMMAND_TAG_OPEN = "<auto-slash-command>"
|
||||
export const AUTO_SLASH_COMMAND_TAG_CLOSE = "</auto-slash-command>"
|
||||
|
||||
export const SLASH_COMMAND_PATTERN = /^\/([a-zA-Z@][\w:@/-]*)\s*(.*)/
|
||||
export const SLASH_COMMAND_PATTERN = /^\/([a-zA-Z@][\w.:@/-]*)\s*(.*)/
|
||||
|
||||
export const EXCLUDED_COMMANDS = new Set([
|
||||
"ralph-loop",
|
||||
|
||||
@@ -35,6 +35,14 @@ function writePluginFixture(baseDir: string): void {
|
||||
description: Run prompt from daplug
|
||||
---
|
||||
Execute daplug prompt flow.
|
||||
`,
|
||||
)
|
||||
writeFileSync(
|
||||
join(pluginInstallPath, "commands", "templated.md"),
|
||||
`---
|
||||
description: Templated prompt from daplug
|
||||
---
|
||||
Echo $ARGUMENTS and \${user_message}.
|
||||
`,
|
||||
)
|
||||
|
||||
@@ -165,4 +173,23 @@ describe("auto-slash command executor plugin dispatch", () => {
|
||||
)
|
||||
expect(result.error).not.toContain("Marketplace plugin commands")
|
||||
})
|
||||
|
||||
it("replaces $ARGUMENTS placeholders in plugin command templates", async () => {
|
||||
const result = await executeSlashCommand(
|
||||
{
|
||||
command: "daplug:templated",
|
||||
args: "ship it",
|
||||
raw: "/daplug:templated ship it",
|
||||
},
|
||||
{
|
||||
skills: [],
|
||||
pluginsEnabled: true,
|
||||
},
|
||||
)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.replacementText).toContain("Echo ship it and ship it.")
|
||||
expect(result.replacementText).not.toContain("$ARGUMENTS")
|
||||
expect(result.replacementText).not.toContain("${user_message}")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,16 +7,12 @@ import {
|
||||
sanitizeModelField,
|
||||
getClaudeConfigDir,
|
||||
getOpenCodeConfigDir,
|
||||
discoverPluginCommandDefinitions,
|
||||
} from "../../shared"
|
||||
import { loadBuiltinCommands } from "../../features/builtin-commands"
|
||||
import type { CommandFrontmatter } from "../../features/claude-code-command-loader/types"
|
||||
import { isMarkdownFile } from "../../shared/file-utils"
|
||||
import { discoverAllSkills, type LoadedSkill, type LazyContentLoader } from "../../features/opencode-skill-loader"
|
||||
import {
|
||||
discoverInstalledPlugins,
|
||||
loadPluginCommands,
|
||||
loadPluginSkillsAsCommands,
|
||||
} from "../../features/claude-code-plugin-loader"
|
||||
import type { ParsedSlashCommand } from "./types"
|
||||
|
||||
interface CommandScope {
|
||||
@@ -109,18 +105,7 @@ export interface ExecutorOptions {
|
||||
}
|
||||
|
||||
function discoverPluginCommands(options?: ExecutorOptions): CommandInfo[] {
|
||||
if (options?.pluginsEnabled === false) {
|
||||
return []
|
||||
}
|
||||
|
||||
const { plugins } = discoverInstalledPlugins({
|
||||
enabledPluginsOverride: options?.enabledPluginsOverride,
|
||||
})
|
||||
|
||||
const pluginDefinitions = {
|
||||
...loadPluginCommands(plugins),
|
||||
...loadPluginSkillsAsCommands(plugins),
|
||||
}
|
||||
const pluginDefinitions = discoverPluginCommandDefinitions(options)
|
||||
|
||||
return Object.entries(pluginDefinitions).map(([name, definition]) => ({
|
||||
name,
|
||||
@@ -216,7 +201,11 @@ async function formatCommandTemplate(cmd: CommandInfo, args: string): Promise<st
|
||||
const commandDir = cmd.path ? dirname(cmd.path) : process.cwd()
|
||||
const withFileRefs = await resolveFileReferencesInText(content, commandDir)
|
||||
const resolvedContent = await resolveCommandsInText(withFileRefs)
|
||||
sections.push(resolvedContent.trim())
|
||||
const resolvedArguments = args
|
||||
const substitutedContent = resolvedContent
|
||||
.replace(/\$\{user_message\}/g, resolvedArguments)
|
||||
.replace(/\$ARGUMENTS/g, resolvedArguments)
|
||||
sections.push(substitutedContent.trim())
|
||||
|
||||
if (args) {
|
||||
sections.push("\n\n---\n")
|
||||
|
||||
Reference in New Issue
Block a user