mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Merge main into v2.0: consolidate critique skill with scoring, personas, and detection
Merges 54 commits from main including factory-based build system, Trae support, improved skill descriptions, and security hardening. Consolidates the critique skill to combine v2.0's sub-agent architecture and automated anti-pattern detection with main's Nielsen heuristics scoring, cognitive load assessment, persona-based testing, and structured follow-up workflow. Fixes browser detector build to create target directory after skill sync. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+62
-19
@@ -64,7 +64,11 @@ export function parseFrontmatter(content) {
|
||||
const value = trimmed.slice(colonIndex + 1).trim();
|
||||
|
||||
if (value) {
|
||||
frontmatter[key] = value === 'true' ? true : value === 'false' ? false : value;
|
||||
// Strip YAML quotes
|
||||
const unquoted = (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))
|
||||
? value.slice(1, -1)
|
||||
: value;
|
||||
frontmatter[key] = unquoted === 'true' ? true : unquoted === 'false' ? false : unquoted;
|
||||
currentKey = key;
|
||||
currentArray = null;
|
||||
} else {
|
||||
@@ -107,7 +111,7 @@ export function readFilesRecursive(dir, fileList = []) {
|
||||
/**
|
||||
* Read and parse all source files (unified skills architecture)
|
||||
* All source lives in source/skills/{name}/SKILL.md
|
||||
* Returns { skills } where each skill has userInvokable flag
|
||||
* Returns { skills } where each skill has userInvocable flag
|
||||
*/
|
||||
export function readSourceFiles(rootDir) {
|
||||
const skillsDir = path.join(rootDir, 'source/skills');
|
||||
@@ -166,8 +170,8 @@ export function readSourceFiles(rootDir) {
|
||||
compatibility: frontmatter.compatibility || '',
|
||||
metadata: frontmatter.metadata || null,
|
||||
allowedTools: frontmatter['allowed-tools'] || '',
|
||||
userInvokable: frontmatter['user-invokable'] === true || frontmatter['user-invokable'] === 'true',
|
||||
args: frontmatter.args || [],
|
||||
userInvocable: frontmatter['user-invocable'] === true || frontmatter['user-invocable'] === 'true',
|
||||
argumentHint: frontmatter['argument-hint'] || '',
|
||||
context: frontmatter.context || null,
|
||||
body,
|
||||
filePath: skillMdPath,
|
||||
@@ -287,42 +291,56 @@ export const PROVIDER_PLACEHOLDERS = {
|
||||
'claude-code': {
|
||||
model: 'Claude',
|
||||
config_file: 'CLAUDE.md',
|
||||
ask_instruction: 'STOP and call the AskUserQuestion tool to clarify.'
|
||||
ask_instruction: 'STOP and call the AskUserQuestion tool to clarify.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'cursor': {
|
||||
model: 'the model',
|
||||
config_file: '.cursorrules',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.'
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'gemini': {
|
||||
model: 'Gemini',
|
||||
config_file: 'GEMINI.md',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.'
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'codex': {
|
||||
model: 'GPT',
|
||||
config_file: 'AGENTS.md',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.'
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '$'
|
||||
},
|
||||
'agents': {
|
||||
model: 'the model',
|
||||
config_file: '.github/copilot-instructions.md',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.'
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'kiro': {
|
||||
model: 'Claude',
|
||||
config_file: '.kiro/settings.json',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.'
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
opencode: {
|
||||
model: 'Claude',
|
||||
config_file: 'AGENTS.md',
|
||||
ask_instruction: 'STOP and call the `question` tool to clarify.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'pi': {
|
||||
model: 'the model',
|
||||
config_file: 'AGENTS.md',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.'
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
},
|
||||
'trae': {
|
||||
model: 'the model',
|
||||
config_file: 'RULES.md',
|
||||
ask_instruction: 'ask the user directly to clarify what you cannot infer.',
|
||||
command_prefix: '/'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -336,8 +354,9 @@ export const PROVIDER_PLACEHOLDERS = {
|
||||
* @param {string} content - The skill body text
|
||||
* @param {string} prefix - The prefix to add (e.g., 'i-')
|
||||
* @param {string[]} skillNames - Array of all skill names
|
||||
* @param {string} commandPrefix - The command invocation prefix (e.g., '/' or '$')
|
||||
*/
|
||||
export function prefixSkillReferences(content, prefix, skillNames) {
|
||||
export function prefixSkillReferences(content, prefix, skillNames, commandPrefix = '/') {
|
||||
if (!prefix || !skillNames || skillNames.length === 0) return content;
|
||||
|
||||
let result = content;
|
||||
@@ -347,11 +366,18 @@ export function prefixSkillReferences(content, prefix, skillNames) {
|
||||
for (const name of sorted) {
|
||||
const prefixed = `${prefix}${name}`;
|
||||
|
||||
// Replace `/skillname` references (command invocations)
|
||||
result = result.replace(new RegExp(`\\/(?=${escapeRegex(name)}(?:[^a-zA-Z0-9_-]|$))`, 'g'), `/${prefix}`);
|
||||
// Replace command invocations (e.g., `/skillname` or `$skillname`) with prefixed versions
|
||||
const escapedPrefix = escapeRegex(commandPrefix);
|
||||
result = result.replace(
|
||||
new RegExp(`${escapedPrefix}(?=${escapeRegex(name)}(?:[^a-zA-Z0-9_-]|$))`, 'g'),
|
||||
`${commandPrefix}${prefix}`
|
||||
);
|
||||
|
||||
// Replace `the skillname skill` references
|
||||
result = result.replace(new RegExp(`the ${escapeRegex(name)} skill`, 'gi'), `the ${prefixed} skill`);
|
||||
result = result.replace(
|
||||
new RegExp(`(the) ${escapeRegex(name)} skill`, 'gi'),
|
||||
(_, article) => `${article} ${prefixed} skill`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -363,18 +389,34 @@ function escapeRegex(str) {
|
||||
|
||||
const EXCLUDED_FROM_SUGGESTIONS = new Set(['teach-impeccable', 'i-teach-impeccable']);
|
||||
|
||||
export function replacePlaceholders(content, provider, commandNames = []) {
|
||||
export function replacePlaceholders(content, provider, commandNames = [], allSkillNames = []) {
|
||||
const placeholders = PROVIDER_PLACEHOLDERS[provider] || PROVIDER_PLACEHOLDERS['cursor'];
|
||||
const cmdPrefix = placeholders.command_prefix || '/';
|
||||
const commandList = commandNames
|
||||
.filter(n => !EXCLUDED_FROM_SUGGESTIONS.has(n))
|
||||
.map(n => `/${n}`)
|
||||
.map(n => `${cmdPrefix}${n}`)
|
||||
.join(', ');
|
||||
|
||||
return content
|
||||
let result = content
|
||||
.replace(/\{\{model\}\}/g, placeholders.model)
|
||||
.replace(/\{\{config_file\}\}/g, placeholders.config_file)
|
||||
.replace(/\{\{ask_instruction\}\}/g, placeholders.ask_instruction)
|
||||
.replace(/\{\{command_prefix\}\}/g, cmdPrefix)
|
||||
.replace(/\{\{available_commands\}\}/g, commandList);
|
||||
|
||||
// Replace `/skillname` invocations with the correct command prefix for this provider
|
||||
// (e.g., `/normalize` → `$normalize` for Codex)
|
||||
if (cmdPrefix !== '/' && allSkillNames.length > 0) {
|
||||
const sorted = [...allSkillNames].sort((a, b) => b.length - a.length);
|
||||
for (const name of sorted) {
|
||||
result = result.replace(
|
||||
new RegExp(`\\/(?=${escapeRegex(name)}(?:[^a-zA-Z0-9_-]|$))`, 'g'),
|
||||
cmdPrefix
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -398,7 +440,8 @@ export function generateYamlFrontmatter(data) {
|
||||
} else if (typeof value === 'boolean') {
|
||||
lines.push(`${key}: ${value}`);
|
||||
} else {
|
||||
lines.push(`${key}: ${value}`);
|
||||
const needsQuoting = typeof value === 'string' && /^[\[{]/.test(value);
|
||||
lines.push(`${key}: ${needsQuoting ? `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : value}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user