feat(commands): add handoffs support for speckit compatibility (#410)

* feat(commands): add handoffs support for speckit compatibility

- Upgrade frontmatter parser to use js-yaml for complex YAML support
- Add HandoffDefinition interface for speckit-style workflow transitions
- Update CommandFrontmatter and CommandDefinition to include handoffs
- Add comprehensive tests for backward compatibility and complex YAML
- Fix type parameters in auto-slash-command and slashcommand tools

Closes #407

* fix(frontmatter): use JSON_SCHEMA for security and add extra fields tolerance tests

- Use JSON_SCHEMA in yaml.load() to prevent code execution via YAML tags
- Add tests to verify extra fields in frontmatter don't cause failures
- Address Greptile security review comment

---------

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
Sisyphus
2026-01-02 15:11:14 +09:00
committed by GitHub
parent 6eaa96f421
commit 99711dacc1
7 changed files with 302 additions and 22 deletions

View File

@@ -1,12 +1,14 @@
export interface FrontmatterResult<T = Record<string, string>> {
import yaml from "js-yaml"
export interface FrontmatterResult<T = Record<string, unknown>> {
data: T
body: string
}
export function parseFrontmatter<T = Record<string, string>>(
export function parseFrontmatter<T = Record<string, unknown>>(
content: string
): FrontmatterResult<T> {
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/
const match = content.match(frontmatterRegex)
if (!match) {
@@ -16,19 +18,12 @@ export function parseFrontmatter<T = Record<string, string>>(
const yamlContent = match[1]
const body = match[2]
const data: Record<string, string | boolean> = {}
for (const line of yamlContent.split("\n")) {
const colonIndex = line.indexOf(":")
if (colonIndex !== -1) {
const key = line.slice(0, colonIndex).trim()
let value: string | boolean = line.slice(colonIndex + 1).trim()
if (value === "true") value = true
else if (value === "false") value = false
data[key] = value
}
try {
// Use JSON_SCHEMA for security - prevents code execution via YAML tags
const parsed = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA })
const data = (parsed ?? {}) as T
return { data, body }
} catch {
return { data: {} as T, body }
}
return { data: data as T, body }
}