Constrain audit/critique to only suggest real commands via {{available_commands}}

Adds a dynamic {{available_commands}} placeholder that the build system
replaces with the actual list of user-invokable skill names. Audit and
critique now explicitly instruct the LLM to only suggest from this list,
preventing hallucinated commands like /redesign.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-05 10:18:52 -08:00
co-authored by Claude Opus 4.6
parent aadaee5c37
commit 870d65b1e3
8 changed files with 24 additions and 13 deletions
+2 -1
View File
@@ -21,6 +21,7 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
let refCount = 0;
for (const skill of skills) {
const skillName = `${prefix}${skill.name}`;
@@ -42,7 +43,7 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
}
const frontmatter = generateYamlFrontmatter(frontmatterObj);
let skillBody = replacePlaceholders(skill.body, 'agents');
let skillBody = replacePlaceholders(skill.body, 'agents', commandNames);
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
const content = `${frontmatter}\n\n${skillBody}`;
const outputPath = path.join(skillDir, 'SKILL.md');
+2 -1
View File
@@ -23,6 +23,7 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
let refCount = 0;
for (const skill of skills) {
const skillName = `${prefix}${skill.name}`;
@@ -41,7 +42,7 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
if (skill.allowedTools) frontmatterObj['allowed-tools'] = skill.allowedTools;
const frontmatter = generateYamlFrontmatter(frontmatterObj);
let skillBody = replacePlaceholders(skill.body, 'claude-code');
let skillBody = replacePlaceholders(skill.body, 'claude-code', commandNames);
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
const content = `${frontmatter}\n\n${skillBody}`;
const outputPath = path.join(skillDir, 'SKILL.md');
+2 -1
View File
@@ -24,6 +24,7 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
let refCount = 0;
for (const skill of skills) {
const skillName = `${prefix}${skill.name}`;
@@ -45,7 +46,7 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
const frontmatter = generateYamlFrontmatter(frontmatterObj);
let skillBody = replacePlaceholders(skill.body, 'codex');
let skillBody = replacePlaceholders(skill.body, 'codex', commandNames);
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
// For user-invokable skills, transform remaining {{argname}} to $ARGNAME
if (skill.userInvokable) {
+2 -1
View File
@@ -23,6 +23,7 @@ export function transformCursor(skills, distDir, patterns = null, options = {})
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
let refCount = 0;
for (const skill of skills) {
const skillName = `${prefix}${skill.name}`;
@@ -35,7 +36,7 @@ export function transformCursor(skills, distDir, patterns = null, options = {})
if (skill.license) frontmatterObj.license = skill.license;
const frontmatter = generateYamlFrontmatter(frontmatterObj);
let skillBody = replacePlaceholders(skill.body, 'cursor');
let skillBody = replacePlaceholders(skill.body, 'cursor', commandNames);
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
const content = `${frontmatter}\n\n${skillBody}`;
const outputPath = path.join(skillDir, 'SKILL.md');
+2 -1
View File
@@ -24,6 +24,7 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
ensureDir(skillsDir);
const allSkillNames = skills.map(s => s.name);
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
let refCount = 0;
for (const skill of skills) {
const skillName = `${prefix}${skill.name}`;
@@ -34,7 +35,7 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
description: skill.description,
});
let skillBody = replacePlaceholders(skill.body, 'gemini');
let skillBody = replacePlaceholders(skill.body, 'gemini', commandNames);
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
// For user-invokable skills, replace remaining {{arg}} placeholders with {{args}}
if (skill.userInvokable) {
+6 -2
View File
@@ -326,13 +326,17 @@ function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
export function replacePlaceholders(content, provider) {
export function replacePlaceholders(content, provider, commandNames = []) {
const placeholders = PROVIDER_PLACEHOLDERS[provider] || PROVIDER_PLACEHOLDERS['cursor'];
const commandList = commandNames.length > 0
? commandNames.map(n => `/${n}`).join(', ')
: '';
return content
.replace(/\{\{model\}\}/g, placeholders.model)
.replace(/\{\{config_file\}\}/g, placeholders.config_file)
.replace(/\{\{ask_instruction\}\}/g, placeholders.ask_instruction);
.replace(/\{\{ask_instruction\}\}/g, placeholders.ask_instruction)
.replace(/\{\{available_commands\}\}/g, commandList);
}
/**