From 846269e3b1f1cccf90cdc7946dec5b9a56e0fd38 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 2 Apr 2026 20:35:18 -0700 Subject: [PATCH] feat: voice-friendly skill triggers for AquaVoice (v0.14.6.0) (#732) * feat: voice-friendly skill triggers for speech-to-text input Add voice-triggers YAML field to 10 SKILL.md.tmpl files with natural-language aliases (e.g. "see-so" for /cso, "tech review" for /plan-eng-review). gen-skill-docs preprocesses voice triggers before transformFrontmatter, folding them into the description and stripping the field from output. Includes unit tests, README voice input section, and CONTRIBUTING.md update. * chore: bump version and changelog (v0.14.6.0) Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 11 ++++++ CONTRIBUTING.md | 2 +- README.md | 6 +++ VERSION | 2 +- autoplan/SKILL.md | 1 + autoplan/SKILL.md.tmpl | 3 ++ benchmark/SKILL.md | 1 + benchmark/SKILL.md.tmpl | 3 ++ codex/SKILL.md | 1 + codex/SKILL.md.tmpl | 4 ++ connect-chrome/SKILL.md | 1 + connect-chrome/SKILL.md.tmpl | 2 + cso/SKILL.md | 1 + cso/SKILL.md.tmpl | 7 ++++ design-html/SKILL.md | 1 + design-html/SKILL.md.tmpl | 4 ++ gstack-upgrade/SKILL.md | 1 + gstack-upgrade/SKILL.md.tmpl | 5 +++ package.json | 2 +- plan-eng-review/SKILL.md | 1 + plan-eng-review/SKILL.md.tmpl | 4 ++ qa-only/SKILL.md | 1 + qa-only/SKILL.md.tmpl | 3 ++ qa/SKILL.md | 1 + qa/SKILL.md.tmpl | 4 ++ scripts/gen-skill-docs.ts | 74 +++++++++++++++++++++++++++++++++-- test/gen-skill-docs.test.ts | 48 +++++++++++++++++++++++ 27 files changed, 188 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 387c6d419cf1c75e220e64b8576fe236fd0984f7..615dbf91023704b20d74e7d23fb5f644b2684149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [0.15.2.0] - 2026-04-02 — Voice-Friendly Skill Triggers + +Say "run a security check" instead of remembering `/cso`. Skills now have voice-friendly trigger phrases that work with AquaVoice, Whisper, and other speech-to-text tools. No more fighting with acronyms that get transcribed wrong ("CSO" -> "CEO" -> wrong skill). + +### Added + +- **Voice triggers for 10 skills.** Each skill gets natural-language aliases baked into its description. "see-so", "security review", "tech review", "code x", "speed test" and more. The right skill activates even when speech-to-text mangles the command name. +- **`voice-triggers:` YAML field in templates.** Structured authoring: add aliases to any `.tmpl` frontmatter, `gen-skill-docs` folds them into the description during generation. Clean source, clean output. +- **Voice input section in README.** New users know skills work with voice from day one. +- **`voice-triggers` documented in CONTRIBUTING.md.** Frontmatter contract updated so contributors know the field exists. + ## [0.15.1.0] - 2026-04-01 — Design Without Shotgun You can now run `/design-html` without having to run `/design-shotgun` first. The skill detects what design context exists (CEO plans, design review artifacts, approved mockups) and asks how you want to proceed. Start from a plan, a description, or a provided PNG, not just an approved mockup. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7b639ee7dd369f008a3da4e5ee8a8201771a31e..f2c67dc9186a7cef258eff8a8455d8af872746f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -254,7 +254,7 @@ bun run build | Aspect | Claude | Codex | |--------|--------|-------| | Output directory | `{skill}/SKILL.md` | `.agents/skills/gstack-{skill}/SKILL.md` (generated at setup, gitignored) | -| Frontmatter | Full (name, description, allowed-tools, hooks, version) | Minimal (name + description only) | +| Frontmatter | Full (name, description, voice-triggers, allowed-tools, hooks, version) | Minimal (name + description only) | | Paths | `~/.claude/skills/gstack` | `$GSTACK_ROOT` (`.agents/skills/gstack` in a repo, otherwise `~/.codex/skills/gstack`) | | Hook skills | `hooks:` frontmatter (enforced by Claude) | Inline safety advisory prose (advisory only) | | `/codex` skill | Included (Claude wraps codex exec) | Excluded (self-referential) | diff --git a/README.md b/README.md index 14147a293a4757e4c2474101fe2b41f04d05e927..e02347d13e20d3ac62b041110c0bd20b575a2b36 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,12 @@ cd ~/gstack && ./setup --host factory Skills install to `~/.factory/skills/gstack-*/`. Restart `droid` to rescan skills, then type `/qa` to get started. +### Voice input (AquaVoice, Whisper, etc.) + +gstack skills have voice-friendly trigger phrases. Say what you want naturally — +"run a security check", "test the website", "do an engineering review" — and the +right skill activates. You don't need to remember slash command names or acronyms. + ## See it work ``` diff --git a/VERSION b/VERSION index dd35abbb63f001f1bb5c784948d36fc5f30eef82..3654b6896220b6723060a6f74a2864be7d53e087 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.15.1.0 +0.15.2.0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index ba72af72f89182e5dbc4ff7988713d81dc44ad35..9acb6d43018d7560d19b131469946038da75b9d7 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -11,6 +11,7 @@ description: | automatically", or "make the decisions for me". Proactively suggest when the user has a plan file and wants to run the full review gauntlet without answering 15-30 intermediate questions. (gstack) + Voice triggers (speech-to-text aliases): "auto plan", "automatic review". benefits-from: [office-hours] allowed-tools: - Bash diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index 41a1d4b34f1f11db35222ab6a24d7ff337e83feb..2193fb397c41d79fa87e8f040813b1fbe9790c50 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -11,6 +11,9 @@ description: | automatically", or "make the decisions for me". Proactively suggest when the user has a plan file and wants to run the full review gauntlet without answering 15-30 intermediate questions. (gstack) +voice-triggers: + - "auto plan" + - "automatic review" benefits-from: [office-hours] allowed-tools: - Bash diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index ea0305be3c0b02e8da1b202ec75f909aa5b93482..c32151e014aab41f575443234b82502263330641 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -8,6 +8,7 @@ description: | Compares before/after on every PR. Tracks performance trends over time. Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals", "bundle size", "load time". (gstack) + Voice triggers (speech-to-text aliases): "speed test", "check performance". allowed-tools: - Bash - Read diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index dca820142613e55a85bfd3589591fcfae4591ea1..afedc1c303b496b25a478fe0d0df132a57400836 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -8,6 +8,9 @@ description: | Compares before/after on every PR. Tracks performance trends over time. Use when: "performance", "benchmark", "page speed", "lighthouse", "web vitals", "bundle size", "load time". (gstack) +voice-triggers: + - "speed test" + - "check performance" allowed-tools: - Bash - Read diff --git a/codex/SKILL.md b/codex/SKILL.md index 77384bdc53b8315f7a5bb521f13f6ab44fe52c55..4bcb510020a811df096df859c8796a1c565740a1 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -8,6 +8,7 @@ description: | your code. Consult: ask codex anything with session continuity for follow-ups. The "200 IQ autistic developer" second opinion. Use when asked to "codex review", "codex challenge", "ask codex", "second opinion", or "consult codex". (gstack) + Voice triggers (speech-to-text aliases): "code x", "code ex", "get another opinion". allowed-tools: - Bash - Read diff --git a/codex/SKILL.md.tmpl b/codex/SKILL.md.tmpl index 86500003c55fedd75ea53ea226aa38ffe2d8e77a..eac1d96ed744c9c7cce01d55a48f96511838f27c 100644 --- a/codex/SKILL.md.tmpl +++ b/codex/SKILL.md.tmpl @@ -8,6 +8,10 @@ description: | your code. Consult: ask codex anything with session continuity for follow-ups. The "200 IQ autistic developer" second opinion. Use when asked to "codex review", "codex challenge", "ask codex", "second opinion", or "consult codex". (gstack) +voice-triggers: + - "code x" + - "code ex" + - "get another opinion" allowed-tools: - Bash - Read diff --git a/connect-chrome/SKILL.md b/connect-chrome/SKILL.md index 48970f807ba2161218ee460d52ca1cdbc4a46222..1297374b7cfc800608ea1f042b3844f168cf827d 100644 --- a/connect-chrome/SKILL.md +++ b/connect-chrome/SKILL.md @@ -7,6 +7,7 @@ description: | action in real time. The extension shows a live activity feed in the Side Panel. Use when asked to "connect chrome", "open chrome", "real browser", "launch chrome", "side panel", or "control my browser". + Voice triggers (speech-to-text aliases): "show me the browser". allowed-tools: - Bash - Read diff --git a/connect-chrome/SKILL.md.tmpl b/connect-chrome/SKILL.md.tmpl index fb338fb18474c1beab406e9908fa96609bff4291..b9b57ff1d7a40694943d66e78cdfa1f92239f54a 100644 --- a/connect-chrome/SKILL.md.tmpl +++ b/connect-chrome/SKILL.md.tmpl @@ -7,6 +7,8 @@ description: | action in real time. The extension shows a live activity feed in the Side Panel. Use when asked to "connect chrome", "open chrome", "real browser", "launch chrome", "side panel", or "control my browser". +voice-triggers: + - "show me the browser" allowed-tools: - Bash - Read diff --git a/cso/SKILL.md b/cso/SKILL.md index b4f093ad818d1c0073f76f500e08a5cc7ae633b4..6540eac1ad9a8cfab4bb6ce12d585a41ed006e6e 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -9,6 +9,7 @@ description: | Two modes: daily (zero-noise, 8/10 confidence gate) and comprehensive (monthly deep scan, 2/10 bar). Trend tracking across audit runs. Use when: "security audit", "threat model", "pentest review", "OWASP", "CSO review". (gstack) + Voice triggers (speech-to-text aliases): "see-so", "see so", "security review", "security check", "vulnerability scan", "run security". allowed-tools: - Bash - Read diff --git a/cso/SKILL.md.tmpl b/cso/SKILL.md.tmpl index b4e9f7bfe61db988fbe72581e1f4646f88adb617..e12a690c202daefa44182670f0e1fcf780a736a8 100644 --- a/cso/SKILL.md.tmpl +++ b/cso/SKILL.md.tmpl @@ -9,6 +9,13 @@ description: | Two modes: daily (zero-noise, 8/10 confidence gate) and comprehensive (monthly deep scan, 2/10 bar). Trend tracking across audit runs. Use when: "security audit", "threat model", "pentest review", "OWASP", "CSO review". (gstack) +voice-triggers: + - "see-so" + - "see so" + - "security review" + - "security check" + - "vulnerability scan" + - "run security" allowed-tools: - Bash - Read diff --git a/design-html/SKILL.md b/design-html/SKILL.md index 100ed65cc23590d404ba12379ec8e4df97f162da..ec8142ed3fd7419c6747f23eacda7c390165d897 100644 --- a/design-html/SKILL.md +++ b/design-html/SKILL.md @@ -11,6 +11,7 @@ description: | for each design type. Use when: "finalize this design", "turn this into HTML", "build me a page", "implement this design", or after any planning skill. Proactively suggest when user has approved a design or has a plan ready. (gstack) + Voice triggers (speech-to-text aliases): "build the design", "code the mockup", "make it real". allowed-tools: - Bash - Read diff --git a/design-html/SKILL.md.tmpl b/design-html/SKILL.md.tmpl index 5b5bb39f8911d3c36911baeadea54d8c97ecb28c..80527c9e55f2c7407fbe4f259760a8f10d2bf28e 100644 --- a/design-html/SKILL.md.tmpl +++ b/design-html/SKILL.md.tmpl @@ -11,6 +11,10 @@ description: | for each design type. Use when: "finalize this design", "turn this into HTML", "build me a page", "implement this design", or after any planning skill. Proactively suggest when user has approved a design or has a plan ready. (gstack) +voice-triggers: + - "build the design" + - "code the mockup" + - "make it real" allowed-tools: - Bash - Read diff --git a/gstack-upgrade/SKILL.md b/gstack-upgrade/SKILL.md index d357e55212e04a3311c9f770207d64618f4f963b..12c3840a6926fd7e93ea473fde70bb28bce763ce 100644 --- a/gstack-upgrade/SKILL.md +++ b/gstack-upgrade/SKILL.md @@ -5,6 +5,7 @@ description: | Upgrade gstack to the latest version. Detects global vs vendored install, runs the upgrade, and shows what's new. Use when asked to "upgrade gstack", "update gstack", or "get latest version". + Voice triggers (speech-to-text aliases): "upgrade the tools", "update the tools", "gee stack upgrade", "g stack upgrade". allowed-tools: - Bash - Read diff --git a/gstack-upgrade/SKILL.md.tmpl b/gstack-upgrade/SKILL.md.tmpl index 7204c75bfd645b2547ccc30838999e1860063de8..9e85478a5a70e99e72132b830db8109286fdcb4d 100644 --- a/gstack-upgrade/SKILL.md.tmpl +++ b/gstack-upgrade/SKILL.md.tmpl @@ -5,6 +5,11 @@ description: | Upgrade gstack to the latest version. Detects global vs vendored install, runs the upgrade, and shows what's new. Use when asked to "upgrade gstack", "update gstack", or "get latest version". +voice-triggers: + - "upgrade the tools" + - "update the tools" + - "gee stack upgrade" + - "g stack upgrade" allowed-tools: - Bash - Read diff --git a/package.json b/package.json index af7d165a1f0d09c1cb731c2b5a33ca25129b4635..f80c3e56faad14c61198570bf69b0ee95dc3fb6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "0.15.1.0", + "version": "0.15.2.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index bf990f52882c78e3a6bc9fbeb7f267dd0a719438..e05d834288b82ad9a614e55af669a6a8d807ecb2 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -9,6 +9,7 @@ description: | "review the architecture", "engineering review", or "lock in the plan". Proactively suggest when the user has a plan or design doc and is about to start coding — to catch architecture issues before implementation. (gstack) + Voice triggers (speech-to-text aliases): "tech review", "technical review", "plan engineering review". benefits-from: [office-hours] allowed-tools: - Read diff --git a/plan-eng-review/SKILL.md.tmpl b/plan-eng-review/SKILL.md.tmpl index fca7535efa36fd285614bea35098a2d8813baed0..cf86f498b1ec3365e53ca9635efbd89b2a118ae1 100644 --- a/plan-eng-review/SKILL.md.tmpl +++ b/plan-eng-review/SKILL.md.tmpl @@ -9,6 +9,10 @@ description: | "review the architecture", "engineering review", or "lock in the plan". Proactively suggest when the user has a plan or design doc and is about to start coding — to catch architecture issues before implementation. (gstack) +voice-triggers: + - "tech review" + - "technical review" + - "plan engineering review" benefits-from: [office-hours] allowed-tools: - Read diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 996b2f3641968e8b59e680b57f2326fd49bfa249..336e5c20deeb07ca058f8ff056d6b7fdadf084e4 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -8,6 +8,7 @@ description: | fixes anything. Use when asked to "just report bugs", "qa report only", or "test but don't fix". For the full test-fix-verify loop, use /qa instead. Proactively suggest when the user wants a bug report without any code changes. (gstack) + Voice triggers (speech-to-text aliases): "bug report", "just check for bugs". allowed-tools: - Bash - Read diff --git a/qa-only/SKILL.md.tmpl b/qa-only/SKILL.md.tmpl index 1aea67e0bf4ed4b4e2e9ec1e1b1d4399ac813a1e..713e0b9c0f76752350a8d93ac6494b6194b0f0f1 100644 --- a/qa-only/SKILL.md.tmpl +++ b/qa-only/SKILL.md.tmpl @@ -8,6 +8,9 @@ description: | fixes anything. Use when asked to "just report bugs", "qa report only", or "test but don't fix". For the full test-fix-verify loop, use /qa instead. Proactively suggest when the user wants a bug report without any code changes. (gstack) +voice-triggers: + - "bug report" + - "just check for bugs" allowed-tools: - Bash - Read diff --git a/qa/SKILL.md b/qa/SKILL.md index 893d0411241f82f7a6d9f2d559c544013983e566..aba5f8f91d0fad3ccc6f01fc5db97fc65944e5df 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -11,6 +11,7 @@ description: | or asks "does this work?". Three tiers: Quick (critical/high only), Standard (+ medium), Exhaustive (+ cosmetic). Produces before/after health scores, fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. (gstack) + Voice triggers (speech-to-text aliases): "quality check", "test the app", "run QA". allowed-tools: - Bash - Read diff --git a/qa/SKILL.md.tmpl b/qa/SKILL.md.tmpl index 697853953ae640ee16d872af07dc1aa8a3a543ab..9afc85485f50631e943b66530736e899e5f72d2e 100644 --- a/qa/SKILL.md.tmpl +++ b/qa/SKILL.md.tmpl @@ -11,6 +11,10 @@ description: | or asks "does this work?". Three tiers: Quick (critical/high only), Standard (+ medium), Exhaustive (+ cosmetic). Produces before/after health scores, fix evidence, and a ship-readiness summary. For report-only mode, use /qa-only. (gstack) +voice-triggers: + - "quality check" + - "test the app" + - "run QA" allowed-tools: - Bash - Read diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index ec495189032d2fc3b6dd17a080156f9a40ab2e6a..32162a33245d7e2c19d693501841a31a9260b504 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -132,6 +132,63 @@ function extractNameAndDescription(content: string): { name: string; description return { name, description }; } +// ─── Voice Trigger Processing ──────────────────────────────── + +/** + * Extract voice-triggers YAML list from frontmatter. + * Returns an array of trigger strings, or [] if no voice-triggers field. + */ +function extractVoiceTriggers(content: string): string[] { + const fmStart = content.indexOf('---\n'); + if (fmStart !== 0) return []; + const fmEnd = content.indexOf('\n---', fmStart + 4); + if (fmEnd === -1) return []; + const frontmatter = content.slice(fmStart + 4, fmEnd); + + const triggers: string[] = []; + let inVoice = false; + for (const line of frontmatter.split('\n')) { + if (/^voice-triggers:/.test(line)) { inVoice = true; continue; } + if (inVoice) { + const m = line.match(/^\s+-\s+"(.+)"$/); + if (m) triggers.push(m[1]); + else if (!/^\s/.test(line)) break; + } + } + return triggers; +} + +/** + * Preprocess voice triggers: fold voice-triggers YAML field into description, + * then strip the field from frontmatter. Must run BEFORE transformFrontmatter + * and extractNameAndDescription so all hosts see the updated description. + */ +function processVoiceTriggers(content: string): string { + const triggers = extractVoiceTriggers(content); + if (triggers.length === 0) return content; + + // Strip voice-triggers block from frontmatter + content = content.replace(/^voice-triggers:\n(?:\s+-\s+"[^"]*"\n?)*/m, ''); + + // Get current description (after stripping voice-triggers, so it's clean) + const { description } = extractNameAndDescription(content); + if (!description) return content; + + // Build new description with voice triggers appended + const voiceLine = `Voice triggers (speech-to-text aliases): ${triggers.map(t => `"${t}"`).join(', ')}.`; + const newDescription = description + '\n' + voiceLine; + + // Replace old indented description with new in frontmatter + const oldIndented = description.split('\n').map(l => ` ${l}`).join('\n'); + const newIndented = newDescription.split('\n').map(l => ` ${l}`).join('\n'); + content = content.replace(oldIndented, newIndented); + + return content; +} + +// Export for testing +export { extractVoiceTriggers, processVoiceTriggers }; + const OPENAI_SHORT_DESCRIPTION_LIMIT = 120; function condenseOpenAIShortDescription(description: string): string { @@ -163,8 +220,10 @@ policy: */ function transformFrontmatter(content: string, host: Host): string { if (host === 'claude') { - // Strip sensitive: field from Claude output (only Factory uses it) - return content.replace(/^sensitive:\s*true\n/m, ''); + // Strip fields not used by Claude: sensitive (Factory-only), voice-triggers (folded into description by preprocessing) + content = content.replace(/^sensitive:\s*true\n/m, ''); + content = content.replace(/^voice-triggers:\n(?:\s+-\s+"[^"]*"\n?)*/m, ''); + return content; } const fmStart = content.indexOf('---\n'); @@ -364,13 +423,22 @@ function processTemplate(tmplPath: string, host: Host = 'claude'): { outputPath: throw new Error(`Unresolved placeholders in ${relTmplPath}: ${remaining.join(', ')}`); } + // Preprocess voice triggers: fold into description, strip field from frontmatter. + // Must run BEFORE transformFrontmatter so all hosts see the updated description, + // and BEFORE extractedDescription is used by external host metadata. + content = processVoiceTriggers(content); + + // Re-extract description AFTER voice trigger preprocessing so Codex openai.yaml + // metadata gets the updated description with voice triggers included. + const postProcessDescription = extractNameAndDescription(content).description; + // For Claude: strip sensitive: field (only Factory uses it) // For external hosts: route output, transform frontmatter, rewrite paths let symlinkLoop = false; if (host === 'claude') { content = transformFrontmatter(content, host); } else { - const result = processExternalHost(content, tmplContent, host, skillDir, extractedDescription, ctx, extractedName || undefined); + const result = processExternalHost(content, tmplContent, host, skillDir, postProcessDescription, ctx, extractedName || undefined); content = result.content; outputPath = result.outputPath; symlinkLoop = result.symlinkLoop; diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index b0a7538f3aaa463f230275ad169785b8416b9b01..adb33456ba19155909623f60485436c3d15cf1aa 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -2581,3 +2581,51 @@ describe('gen-skill-docs prefix warning (#620/#578)', () => { } }); }); + +describe('voice-triggers processing', () => { + const { extractVoiceTriggers, processVoiceTriggers } = require('../scripts/gen-skill-docs') as { + extractVoiceTriggers: (content: string) => string[]; + processVoiceTriggers: (content: string) => string; + }; + + test('extractVoiceTriggers parses valid YAML list', () => { + const content = `---\nname: cso\ndescription: |\n Security audit.\nvoice-triggers:\n - "see-so"\n - "security review"\n---\nBody`; + const triggers = extractVoiceTriggers(content); + expect(triggers).toEqual(['see-so', 'security review']); + }); + + test('extractVoiceTriggers returns [] when no field present', () => { + const content = `---\nname: qa\ndescription: |\n QA testing.\n---\nBody`; + expect(extractVoiceTriggers(content)).toEqual([]); + }); + + test('processVoiceTriggers appends voice triggers to description', () => { + const content = `---\nname: cso\ndescription: |\n Security audit. (gstack)\nvoice-triggers:\n - "see-so"\n - "security review"\n---\nBody`; + const result = processVoiceTriggers(content); + expect(result).toContain('Voice triggers (speech-to-text aliases): "see-so", "security review".'); + }); + + test('processVoiceTriggers strips voice-triggers field from output', () => { + const content = `---\nname: cso\ndescription: |\n Security audit. (gstack)\nvoice-triggers:\n - "see-so"\n---\nBody`; + const result = processVoiceTriggers(content); + expect(result).not.toContain('voice-triggers:'); + }); + + test('processVoiceTriggers returns content unchanged when no voice-triggers', () => { + const content = `---\nname: qa\ndescription: |\n QA testing.\n---\nBody`; + expect(processVoiceTriggers(content)).toBe(content); + }); + + test('generated CSO SKILL.md contains voice triggers in description', () => { + const content = fs.readFileSync(path.join(ROOT, 'cso', 'SKILL.md'), 'utf-8'); + expect(content).toContain('"see-so"'); + expect(content).toContain('Voice triggers (speech-to-text aliases):'); + }); + + test('generated CSO SKILL.md does NOT contain raw voice-triggers field', () => { + const content = fs.readFileSync(path.join(ROOT, 'cso', 'SKILL.md'), 'utf-8'); + const fmEnd = content.indexOf('\n---', 4); + const frontmatter = content.slice(0, fmEnd); + expect(frontmatter).not.toContain('voice-triggers:'); + }); +});