feat(schema): add compound trigger format validation
This commit is contained in:
parent
0edcebc517
commit
15d34d7ea5
|
|
@ -35,14 +35,14 @@ agent:
|
|||
- "NEVER lie about tests being written or passing - tests must actually exist and pass 100%"
|
||||
|
||||
menu:
|
||||
- trigger: DS or dev-story or fuzzy match on dev story
|
||||
- trigger: DS or dev-story or fuzzy match on dev-story
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml"
|
||||
description: "[DS] Execute Dev Story workflow (full BMM path with sprint-status)"
|
||||
|
||||
- trigger: CR or code-review or fuzzy match on code review
|
||||
- trigger: CR or code-review or fuzzy match on code-review
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/4-implementation/code-review/workflow.yaml"
|
||||
description: "[CR] Perform a thorough clean context code review (Highly Recommended, use fresh context and different LLM)"
|
||||
|
||||
- trigger: PS or party-mode or fuzzy match on party mode
|
||||
- trigger: PM or party-mode or fuzzy match on party-mode
|
||||
exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md"
|
||||
description: "[PS] Bring the whole team in to chat with other expert agents from the party"
|
||||
description: "[PM] Bring the whole team in to chat with other expert agents from the party"
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ agent:
|
|||
- If `**/project-context.md` exists, follow it. If absent, proceed without.
|
||||
|
||||
menu:
|
||||
- trigger: TS or tech-spec or fuzzy match on tech spec
|
||||
- trigger: TS or tech-spec or fuzzy match on tech-spec
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/bmad-quick-flow/create-tech-spec/workflow.yaml"
|
||||
description: "[TS] Architect a technical spec with implementation-ready stories (Required first step)"
|
||||
|
||||
- trigger: QD or quick-dev or fuzzy match on quick dev
|
||||
- trigger: QD or quick-dev or fuzzy match on quick-dev
|
||||
workflow: "{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml"
|
||||
description: "[QD] Implement the tech spec end-to-end solo (Core of Quick Flow)"
|
||||
|
|
|
|||
24
test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml
vendored
Normal file
24
test/fixtures/agent-schema/invalid/menu-triggers/compound-invalid-format.agent.yaml
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Test: Compound trigger with invalid format
|
||||
# Expected: FAIL
|
||||
# Error code: custom
|
||||
# Error path: agent.menu[0].trigger
|
||||
# Error message: agent.menu[].trigger compound format error: invalid compound trigger format
|
||||
|
||||
agent:
|
||||
metadata:
|
||||
id: compound-invalid-format
|
||||
name: Invalid Format
|
||||
title: Invalid Format Test
|
||||
icon: 🧪
|
||||
|
||||
persona:
|
||||
role: Test agent
|
||||
identity: Test identity
|
||||
communication_style: Test style
|
||||
principles:
|
||||
- Test principle
|
||||
|
||||
menu:
|
||||
- trigger: TS or tech-spec
|
||||
description: Missing fuzzy match clause
|
||||
action: test
|
||||
24
test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml
vendored
Normal file
24
test/fixtures/agent-schema/invalid/menu-triggers/compound-mismatched-kebab.agent.yaml
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Test: Compound trigger with mismatched kebab portions
|
||||
# Expected: FAIL
|
||||
# Error code: custom
|
||||
# Error path: agent.menu[0].trigger
|
||||
# Error message: agent.menu[].trigger compound format error: kebab-case trigger mismatch: "tech-spec" vs "other-thing"
|
||||
|
||||
agent:
|
||||
metadata:
|
||||
id: compound-mismatched-kebab
|
||||
name: Mismatched Kebab
|
||||
title: Mismatched Kebab Test
|
||||
icon: 🧪
|
||||
|
||||
persona:
|
||||
role: Test agent
|
||||
identity: Test identity
|
||||
communication_style: Test style
|
||||
principles:
|
||||
- Test principle
|
||||
|
||||
menu:
|
||||
- trigger: TS or tech-spec or fuzzy match on other-thing
|
||||
description: Kebab portions do not match
|
||||
action: test
|
||||
24
test/fixtures/agent-schema/invalid/menu-triggers/compound-wrong-shortcut.agent.yaml
vendored
Normal file
24
test/fixtures/agent-schema/invalid/menu-triggers/compound-wrong-shortcut.agent.yaml
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Test: Compound trigger with wrong shortcut
|
||||
# Expected: FAIL
|
||||
# Error code: custom
|
||||
# Error path: agent.menu[0].trigger
|
||||
# Error message: agent.menu[].trigger compound format error: shortcut "XX" does not match expected "TS" for "tech-spec"
|
||||
|
||||
agent:
|
||||
metadata:
|
||||
id: compound-wrong-shortcut
|
||||
name: Wrong Shortcut
|
||||
title: Wrong Shortcut Test
|
||||
icon: 🧪
|
||||
|
||||
persona:
|
||||
role: Test agent
|
||||
identity: Test identity
|
||||
communication_style: Test style
|
||||
principles:
|
||||
- Test principle
|
||||
|
||||
menu:
|
||||
- trigger: XX or tech-spec or fuzzy match on tech-spec
|
||||
description: Shortcut does not match kebab trigger
|
||||
action: test
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# Test: Valid compound triggers
|
||||
# Expected: PASS
|
||||
|
||||
agent:
|
||||
metadata:
|
||||
id: compound-triggers
|
||||
name: Compound Triggers
|
||||
title: Compound Triggers Test
|
||||
icon: 🧪
|
||||
|
||||
persona:
|
||||
role: Test agent with compound triggers
|
||||
identity: I test compound trigger validation.
|
||||
communication_style: Clear
|
||||
principles:
|
||||
- Test compound format
|
||||
|
||||
menu:
|
||||
- trigger: TS or tech-spec or fuzzy match on tech-spec
|
||||
description: Two-word compound trigger
|
||||
action: tech_spec
|
||||
- trigger: DS or dev-story or fuzzy match on dev-story
|
||||
description: Another two-word compound trigger
|
||||
action: dev_story
|
||||
- trigger: WI or workflow-init-process or fuzzy match on workflow-init-process
|
||||
description: Three-word compound trigger (uses first 2 words for shortcut)
|
||||
action: workflow_init
|
||||
- trigger: H or help or fuzzy match on help
|
||||
description: Single-word compound trigger (1-letter shortcut)
|
||||
action: help
|
||||
|
|
@ -4,6 +4,56 @@ const { z } = require('zod');
|
|||
|
||||
const COMMAND_TARGET_KEYS = ['workflow', 'validate-workflow', 'exec', 'action', 'tmpl', 'data'];
|
||||
const TRIGGER_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||
const COMPOUND_TRIGGER_PATTERN = /^([A-Z]{1,2}) or ([a-z0-9]+(?:-[a-z0-9]+)*) or fuzzy match on ([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
||||
|
||||
/**
|
||||
* Derive the expected shortcut from a kebab-case trigger.
|
||||
* - Single word: first letter (e.g., "help" → "H")
|
||||
* - Multi-word: first letter of first two words (e.g., "tech-spec" → "TS")
|
||||
* @param {string} kebabTrigger The kebab-case trigger name.
|
||||
* @returns {string} The expected uppercase shortcut.
|
||||
*/
|
||||
function deriveShortcutFromKebab(kebabTrigger) {
|
||||
const words = kebabTrigger.split('-');
|
||||
if (words.length === 1) {
|
||||
return words[0][0].toUpperCase();
|
||||
}
|
||||
return (words[0][0] + words[1][0]).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and validate a compound trigger string.
|
||||
* Format: "<SHORTCUT> or <kebab-case> or fuzzy match on <kebab-case>"
|
||||
* @param {string} triggerValue The trigger string to parse.
|
||||
* @returns {{ valid: boolean, kebabTrigger?: string, error?: string }}
|
||||
*/
|
||||
function parseCompoundTrigger(triggerValue) {
|
||||
const match = COMPOUND_TRIGGER_PATTERN.exec(triggerValue);
|
||||
if (!match) {
|
||||
return { valid: false, error: 'invalid compound trigger format' };
|
||||
}
|
||||
|
||||
const [, shortcut, kebabTrigger, fuzzyKebab] = match;
|
||||
|
||||
// Validate both kebab instances are identical
|
||||
if (kebabTrigger !== fuzzyKebab) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `kebab-case trigger mismatch: "${kebabTrigger}" vs "${fuzzyKebab}"`,
|
||||
};
|
||||
}
|
||||
|
||||
// Validate shortcut matches derived value
|
||||
const expectedShortcut = deriveShortcutFromKebab(kebabTrigger);
|
||||
if (shortcut !== expectedShortcut) {
|
||||
return {
|
||||
valid: false,
|
||||
error: `shortcut "${shortcut}" does not match expected "${expectedShortcut}" for "${kebabTrigger}"`,
|
||||
};
|
||||
}
|
||||
|
||||
return { valid: true, kebabTrigger };
|
||||
}
|
||||
|
||||
// Public API ---------------------------------------------------------------
|
||||
|
||||
|
|
@ -52,8 +102,21 @@ function agentSchema(options = {}) {
|
|||
// Handle legacy format with trigger field
|
||||
if (item.trigger) {
|
||||
const triggerValue = item.trigger;
|
||||
let canonicalTrigger = triggerValue;
|
||||
|
||||
if (!TRIGGER_PATTERN.test(triggerValue)) {
|
||||
// Check if it's a compound trigger (contains " or ")
|
||||
if (triggerValue.includes(' or ')) {
|
||||
const result = parseCompoundTrigger(triggerValue);
|
||||
if (!result.valid) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: ['agent', 'menu', index, 'trigger'],
|
||||
message: `agent.menu[].trigger compound format error: ${result.error}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
canonicalTrigger = result.kebabTrigger;
|
||||
} else if (!TRIGGER_PATTERN.test(triggerValue)) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: ['agent', 'menu', index, 'trigger'],
|
||||
|
|
@ -62,16 +125,16 @@ function agentSchema(options = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (seenTriggers.has(triggerValue)) {
|
||||
if (seenTriggers.has(canonicalTrigger)) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: ['agent', 'menu', index, 'trigger'],
|
||||
message: `agent.menu[].trigger duplicates "${triggerValue}" within the same agent`,
|
||||
message: `agent.menu[].trigger duplicates "${canonicalTrigger}" within the same agent`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
seenTriggers.add(triggerValue);
|
||||
seenTriggers.add(canonicalTrigger);
|
||||
}
|
||||
// Handle multi format with triggers array (new format)
|
||||
else if (item.triggers && Array.isArray(item.triggers)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue