Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.15.1.0
0.15.2.0
1 change: 1 addition & 0 deletions autoplan/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions autoplan/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions benchmark/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions benchmark/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions codex/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions codex/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions connect-chrome/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions connect-chrome/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions cso/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions cso/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions design-html/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions design-html/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions gstack-upgrade/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions gstack-upgrade/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions plan-eng-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions plan-eng-review/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions qa-only/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions qa-only/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions qa/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions qa/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 71 additions & 3 deletions scripts/gen-skill-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down
48 changes: 48 additions & 0 deletions test/gen-skill-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:');
});
});
Loading