Move PID file to project root (.impeccable-live.json)

os.tmpdir() returns /var/folders/.../T/ on macOS, not /tmp/. The skill
reference was telling the agent to cat /tmp/impeccable-live.json which
didn't exist. Moving the PID file to the project root makes it
predictable across platforms and project-scoped (multiple projects can
run independent live sessions).

Changed in: live-server.mjs, live-poll.mjs, live.md reference.
Added .impeccable-live.json to .gitignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-13 12:32:42 -07:00
co-authored by Claude Opus 4.6
parent 4b52756edd
commit 4092ee5f22
108 changed files with 8432 additions and 126 deletions
+25 -4
View File
@@ -7,8 +7,9 @@
* - Cursor: .cursor/skills/
* - Claude Code: .claude/skills/
* - Gemini: .gemini/skills/
* - Codex: .codex/skills/
* - Agents: .agents/skills/ (VS Code Copilot + Antigravity)
* - Codex: .codex/skills/ (Codex-specific compatibility bundle)
* - Agents: .agents/skills/ (Codex repo/user installs)
* - GitHub: .github/skills/ (GitHub Copilot)
*
* Also assembles a universal ZIP containing all providers,
* and builds Tailwind CSS for production deployment.
@@ -111,6 +112,19 @@ function generateCounts(rootDir, skills, buildDir) {
return errors;
}
function validateSkillFrontmatter(skills) {
let errors = 0;
for (const skill of skills) {
if (skill.description && skill.description.length > 1024) {
console.error(`${skill.filePath}: invalid description: exceeds maximum length of 1024 characters (${skill.description.length})`);
errors++;
}
}
return errors;
}
/**
* Cross-validate that every detection rule with a `skillGuideline` has a
* matching DON'T line in the right section of source/skills/impeccable/SKILL.md.
@@ -406,8 +420,9 @@ This folder contains skills for all supported tools:
.cursor/ -> Cursor
.claude/ -> Claude Code
.gemini/ -> Gemini CLI
.codex/ -> Codex CLI
.agents/ -> VS Code Copilot, Antigravity
.codex/ -> Codex compatibility bundle
.agents/ -> Codex CLI
.github/ -> GitHub Copilot
.kiro/ -> Kiro
.opencode/ -> OpenCode
.pi/ -> Pi
@@ -415,6 +430,7 @@ This folder contains skills for all supported tools:
.trae/ -> Trae International
To install, copy the relevant folder(s) into your project root.
For Codex, repo and user skill installs come from .agents/skills.
These are hidden folders (dotfiles). Press Cmd+Shift+. in Finder to see them.
`);
@@ -638,6 +654,11 @@ async function build() {
const userInvocableCount = skills.filter(s => s.userInvocable).length;
console.log(`📖 Read ${skills.length} skills (${userInvocableCount} user-invocable) and ${patterns.patterns.length + patterns.antipatterns.length} pattern categories\n`);
const frontmatterErrors = validateSkillFrontmatter(skills);
if (frontmatterErrors > 0) {
process.exit(1);
}
// Read skills version from plugin.json
const pluginJson = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, '.claude-plugin/plugin.json'), 'utf-8'));
const skillsVersion = pluginJson.version;
+33 -3
View File
@@ -1,5 +1,5 @@
import path from 'path';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceholders } from '../utils.js';
import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, generateYamlDocument, replacePlaceholders } from '../utils.js';
import { SKILL_CATEGORIES, CATEGORY_ORDER } from '../sub-pages-data.js';
/**
@@ -40,6 +40,31 @@ const FIELD_SPECS = {
},
};
function humanizeSkillName(name) {
return name
.split('-')
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}
function summarizeDescription(description, maxLength = 88) {
if (!description || description.length <= maxLength) return description;
const clipped = description.slice(0, maxLength - 1);
const lastSpace = clipped.lastIndexOf(' ');
return `${(lastSpace > 48 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}...`;
}
function buildOpenAIMetadata(skill) {
const displayName = humanizeSkillName(skill.name);
return {
interface: {
display_name: displayName,
short_description: summarizeDescription(skill.description),
default_prompt: `Use ${displayName} to redesign, critique, audit, or polish this frontend.`,
},
};
}
/**
* Create a transformer function for a given provider config.
*
@@ -47,7 +72,7 @@ const FIELD_SPECS = {
* @returns {Function} transform(skills, distDir, options?)
*/
export function createTransformer(config) {
const { provider, configDir, displayName, frontmatterFields = [], bodyTransform, placeholderProvider } = config;
const { provider, configDir, displayName, frontmatterFields = [], bodyTransform, placeholderProvider, writeOpenAIMetadata = false, includeVersion = true } = config;
const placeholderKey = placeholderProvider || provider;
const activeFields = frontmatterFields
@@ -79,7 +104,7 @@ export function createTransformer(config) {
name: skillName,
description: skill.description,
};
if (skillsVersion) frontmatterObj.version = skillsVersion;
if (skillsVersion && includeVersion) frontmatterObj.version = skillsVersion;
for (const spec of activeFields) {
if (spec.condition && !spec.condition(skill)) continue;
@@ -118,6 +143,11 @@ export function createTransformer(config) {
const content = `${frontmatter}\n\n${skillBody}`;
writeFile(path.join(skillDir, 'SKILL.md'), content);
if (writeOpenAIMetadata) {
const openaiMetadata = buildOpenAIMetadata(skill);
writeFile(path.join(skillDir, 'agents', 'openai.yaml'), generateYamlDocument(openaiMetadata));
}
// Copy reference files
if (skill.references && skill.references.length > 0) {
const refDir = path.join(skillDir, 'reference');
+1
View File
@@ -9,6 +9,7 @@ export const transformClaudeCode = createTransformer(PROVIDERS['claude-code']);
export const transformGemini = createTransformer(PROVIDERS.gemini);
export const transformCodex = createTransformer(PROVIDERS.codex);
export const transformAgents = createTransformer(PROVIDERS.agents);
export const transformGitHub = createTransformer(PROVIDERS.github);
export const transformKiro = createTransformer(PROVIDERS.kiro);
export const transformOpenCode = createTransformer(PROVIDERS.opencode);
export const transformPi = createTransformer(PROVIDERS.pi);
+14 -2
View File
@@ -31,12 +31,24 @@ export const PROVIDERS = {
provider: 'codex',
configDir: '.codex',
displayName: 'Codex',
frontmatterFields: ['argument-hint', 'license'],
frontmatterFields: [],
includeVersion: false,
writeOpenAIMetadata: true,
},
agents: {
provider: 'agents',
configDir: '.agents',
displayName: 'Agents',
displayName: 'Codex Repo Skills',
placeholderProvider: 'codex',
frontmatterFields: [],
includeVersion: false,
writeOpenAIMetadata: true,
},
github: {
provider: 'github',
configDir: '.github',
displayName: 'GitHub Copilot',
placeholderProvider: 'agents',
frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata'],
},
kiro: {
+34
View File
@@ -475,6 +475,31 @@ function formatYamlScalar(value) {
return value;
}
function appendYamlObject(lines, data, indent = 0) {
const space = ' '.repeat(indent);
for (const [key, value] of Object.entries(data)) {
if (Array.isArray(value)) {
lines.push(`${space}${key}:`);
for (const item of value) {
if (item && typeof item === 'object' && !Array.isArray(item)) {
lines.push(`${space} -`);
appendYamlObject(lines, item, indent + 4);
} else {
lines.push(`${space} - ${formatYamlScalar(item)}`);
}
}
} else if (value && typeof value === 'object') {
lines.push(`${space}${key}:`);
appendYamlObject(lines, value, indent + 2);
} else if (typeof value === 'boolean') {
lines.push(`${space}${key}: ${value}`);
} else {
lines.push(`${space}${key}: ${formatYamlScalar(value)}`);
}
}
}
/**
* Generate YAML frontmatter string
*/
@@ -503,3 +528,12 @@ export function generateYamlFrontmatter(data) {
lines.push('---');
return lines.join('\n');
}
/**
* Generate a plain YAML document string.
*/
export function generateYamlDocument(data) {
const lines = [];
appendYamlObject(lines, data);
return lines.join('\n');
}