mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-08 18:33:28 +08:00
fix: resolve ESLint errors and update tests for project-name fallback
- Fix 16 ESLint no-unused-vars errors across hook scripts and tests - Add eslint-disable comment for intentional control-regex in ANSI stripper - Update session file test to use getSessionIdShort() instead of hardcoded 'default' (reflects PR #110's project-name fallback behavior) - Add marketing/ to .gitignore (local drafts) - Add skill-create-output.js (terminal output formatter) All 69 tests now pass. CI should be green.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ private/
|
|||||||
|
|
||||||
# Session templates (not committed)
|
# Session templates (not committed)
|
||||||
examples/sessions/*.tmp
|
examples/sessions/*.tmp
|
||||||
|
|
||||||
|
# Local drafts
|
||||||
|
marketing/
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ process.stdin.on('end', () => {
|
|||||||
if (hasConsole) {
|
if (hasConsole) {
|
||||||
console.error('[Hook] Remove console.log statements before committing');
|
console.error('[Hook] Remove console.log statements before committing');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (_error) {
|
||||||
// Silently ignore errors (git might not be available, etc.)
|
// Silently ignore errors (git might not be available, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ const {
|
|||||||
getTimeString,
|
getTimeString,
|
||||||
getSessionIdShort,
|
getSessionIdShort,
|
||||||
ensureDir,
|
ensureDir,
|
||||||
readFile,
|
|
||||||
writeFile,
|
writeFile,
|
||||||
replaceInFile,
|
replaceInFile,
|
||||||
log
|
log
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
* files and notifies Claude of available context to load.
|
* files and notifies Claude of available context to load.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const {
|
const {
|
||||||
getSessionsDir,
|
getSessionsDir,
|
||||||
getLearnedSkillsDir,
|
getLearnedSkillsDir,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
|
||||||
const {
|
const {
|
||||||
getTempDir,
|
getTempDir,
|
||||||
readFile,
|
readFile,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { commandExists, getClaudeDir, readFile, writeFile, log, runCommand } = require('./utils');
|
const { commandExists, getClaudeDir, readFile, writeFile } = require('./utils');
|
||||||
|
|
||||||
// Package manager definitions
|
// Package manager definitions
|
||||||
const PACKAGE_MANAGERS = {
|
const PACKAGE_MANAGERS = {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ function findFiles(dir, pattern, options = {}) {
|
|||||||
searchDir(fullPath);
|
searchDir(fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
// Ignore permission errors
|
// Ignore permission errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,8 @@ const {
|
|||||||
setProjectPackageManager,
|
setProjectPackageManager,
|
||||||
getAvailablePackageManagers,
|
getAvailablePackageManagers,
|
||||||
detectFromLockFile,
|
detectFromLockFile,
|
||||||
detectFromPackageJson,
|
detectFromPackageJson
|
||||||
getSelectionPrompt
|
|
||||||
} = require('./lib/package-manager');
|
} = require('./lib/package-manager');
|
||||||
const { log } = require('./lib/utils');
|
|
||||||
|
|
||||||
function showHelp() {
|
function showHelp() {
|
||||||
console.log(`
|
console.log(`
|
||||||
|
|||||||
244
scripts/skill-create-output.js
Normal file
244
scripts/skill-create-output.js
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Skill Creator - Pretty Output Formatter
|
||||||
|
*
|
||||||
|
* Creates beautiful terminal output for the /skill-create command
|
||||||
|
* similar to @mvanhorn's /last30days skill
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ANSI color codes - no external dependencies
|
||||||
|
const chalk = {
|
||||||
|
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
||||||
|
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
||||||
|
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
||||||
|
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
||||||
|
magenta: (s) => `\x1b[35m${s}\x1b[0m`,
|
||||||
|
gray: (s) => `\x1b[90m${s}\x1b[0m`,
|
||||||
|
white: (s) => `\x1b[37m${s}\x1b[0m`,
|
||||||
|
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
||||||
|
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
||||||
|
bgCyan: (s) => `\x1b[46m${s}\x1b[0m`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Box drawing characters
|
||||||
|
const BOX = {
|
||||||
|
topLeft: '╭',
|
||||||
|
topRight: '╮',
|
||||||
|
bottomLeft: '╰',
|
||||||
|
bottomRight: '╯',
|
||||||
|
horizontal: '─',
|
||||||
|
vertical: '│',
|
||||||
|
verticalRight: '├',
|
||||||
|
verticalLeft: '┤',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Progress spinner frames
|
||||||
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
function box(title, content, width = 60) {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const top = `${BOX.topLeft}${BOX.horizontal} ${chalk.bold(chalk.cyan(title))} ${BOX.horizontal.repeat(width - title.length - 5)}${BOX.topRight}`;
|
||||||
|
const bottom = `${BOX.bottomLeft}${BOX.horizontal.repeat(width - 1)}${BOX.bottomRight}`;
|
||||||
|
const middle = lines.map(line => {
|
||||||
|
const padding = width - 3 - stripAnsi(line).length;
|
||||||
|
return `${BOX.vertical} ${line}${' '.repeat(Math.max(0, padding))} ${BOX.vertical}`;
|
||||||
|
}).join('\n');
|
||||||
|
return `${top}\n${middle}\n${bottom}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripAnsi(str) {
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function progressBar(percent, width = 30) {
|
||||||
|
const filled = Math.round(width * percent / 100);
|
||||||
|
const empty = width - filled;
|
||||||
|
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
||||||
|
return `${bar} ${chalk.bold(percent)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function animateProgress(label, steps, callback) {
|
||||||
|
process.stdout.write(`\n${chalk.cyan('⏳')} ${label}...\n`);
|
||||||
|
|
||||||
|
for (let i = 0; i < steps.length; i++) {
|
||||||
|
const step = steps[i];
|
||||||
|
process.stdout.write(` ${chalk.gray(SPINNER[i % SPINNER.length])} ${step.name}`);
|
||||||
|
await sleep(step.duration || 500);
|
||||||
|
process.stdout.clearLine?.(0) || process.stdout.write('\r');
|
||||||
|
process.stdout.cursorTo?.(0) || process.stdout.write('\r');
|
||||||
|
process.stdout.write(` ${chalk.green('✓')} ${step.name}\n`);
|
||||||
|
if (callback) callback(step, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main output formatter
|
||||||
|
class SkillCreateOutput {
|
||||||
|
constructor(repoName, options = {}) {
|
||||||
|
this.repoName = repoName;
|
||||||
|
this.options = options;
|
||||||
|
this.width = options.width || 70;
|
||||||
|
}
|
||||||
|
|
||||||
|
header() {
|
||||||
|
const subtitle = `Extracting patterns from ${chalk.cyan(this.repoName)}`;
|
||||||
|
|
||||||
|
console.log('\n');
|
||||||
|
console.log(chalk.bold(chalk.magenta('╔════════════════════════════════════════════════════════════════╗')));
|
||||||
|
console.log(chalk.bold(chalk.magenta('║')) + chalk.bold(' 🔮 ECC Skill Creator ') + chalk.bold(chalk.magenta('║')));
|
||||||
|
console.log(chalk.bold(chalk.magenta('║')) + ` ${subtitle}${' '.repeat(Math.max(0, 55 - stripAnsi(subtitle).length))}` + chalk.bold(chalk.magenta('║')));
|
||||||
|
console.log(chalk.bold(chalk.magenta('╚════════════════════════════════════════════════════════════════╝')));
|
||||||
|
console.log('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async analyzePhase(data) {
|
||||||
|
const steps = [
|
||||||
|
{ name: 'Parsing git history...', duration: 300 },
|
||||||
|
{ name: `Found ${chalk.yellow(data.commits)} commits`, duration: 200 },
|
||||||
|
{ name: 'Analyzing commit patterns...', duration: 400 },
|
||||||
|
{ name: 'Detecting file co-changes...', duration: 300 },
|
||||||
|
{ name: 'Identifying workflows...', duration: 400 },
|
||||||
|
{ name: 'Extracting architecture patterns...', duration: 300 },
|
||||||
|
];
|
||||||
|
|
||||||
|
await animateProgress('Analyzing Repository', steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
analysisResults(data) {
|
||||||
|
console.log('\n');
|
||||||
|
console.log(box('📊 Analysis Results', `
|
||||||
|
${chalk.bold('Commits Analyzed:')} ${chalk.yellow(data.commits)}
|
||||||
|
${chalk.bold('Time Range:')} ${chalk.gray(data.timeRange)}
|
||||||
|
${chalk.bold('Contributors:')} ${chalk.cyan(data.contributors)}
|
||||||
|
${chalk.bold('Files Tracked:')} ${chalk.green(data.files)}
|
||||||
|
`));
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns(patterns) {
|
||||||
|
console.log('\n');
|
||||||
|
console.log(chalk.bold(chalk.cyan('🔍 Key Patterns Discovered:')));
|
||||||
|
console.log(chalk.gray('─'.repeat(50)));
|
||||||
|
|
||||||
|
patterns.forEach((pattern, i) => {
|
||||||
|
const confidence = pattern.confidence || 0.8;
|
||||||
|
const confidenceBar = progressBar(Math.round(confidence * 100), 15);
|
||||||
|
console.log(`
|
||||||
|
${chalk.bold(chalk.yellow(`${i + 1}.`))} ${chalk.bold(pattern.name)}
|
||||||
|
${chalk.gray('Trigger:')} ${pattern.trigger}
|
||||||
|
${chalk.gray('Confidence:')} ${confidenceBar}
|
||||||
|
${chalk.dim(pattern.evidence)}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
instincts(instincts) {
|
||||||
|
console.log('\n');
|
||||||
|
console.log(box('🧠 Instincts Generated', instincts.map((inst, i) =>
|
||||||
|
`${chalk.yellow(`${i + 1}.`)} ${chalk.bold(inst.name)} ${chalk.gray(`(${Math.round(inst.confidence * 100)}%)`)}`
|
||||||
|
).join('\n')));
|
||||||
|
}
|
||||||
|
|
||||||
|
output(skillPath, instinctsPath) {
|
||||||
|
console.log('\n');
|
||||||
|
console.log(chalk.bold(chalk.green('✨ Generation Complete!')));
|
||||||
|
console.log(chalk.gray('─'.repeat(50)));
|
||||||
|
console.log(`
|
||||||
|
${chalk.green('📄')} ${chalk.bold('Skill File:')}
|
||||||
|
${chalk.cyan(skillPath)}
|
||||||
|
|
||||||
|
${chalk.green('🧠')} ${chalk.bold('Instincts File:')}
|
||||||
|
${chalk.cyan(instinctsPath)}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextSteps() {
|
||||||
|
console.log(box('📋 Next Steps', `
|
||||||
|
${chalk.yellow('1.')} Review the generated SKILL.md
|
||||||
|
${chalk.yellow('2.')} Import instincts: ${chalk.cyan('/instinct-import <path>')}
|
||||||
|
${chalk.yellow('3.')} View learned patterns: ${chalk.cyan('/instinct-status')}
|
||||||
|
${chalk.yellow('4.')} Evolve into skills: ${chalk.cyan('/evolve')}
|
||||||
|
`));
|
||||||
|
console.log('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
footer() {
|
||||||
|
console.log(chalk.gray('─'.repeat(60)));
|
||||||
|
console.log(chalk.dim(` Powered by Everything Claude Code • ecc.tools`));
|
||||||
|
console.log(chalk.dim(` GitHub App: github.com/apps/skill-creator`));
|
||||||
|
console.log('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo function to show the output
|
||||||
|
async function demo() {
|
||||||
|
const output = new SkillCreateOutput('PMX');
|
||||||
|
|
||||||
|
output.header();
|
||||||
|
|
||||||
|
await output.analyzePhase({
|
||||||
|
commits: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
output.analysisResults({
|
||||||
|
commits: 200,
|
||||||
|
timeRange: 'Nov 2024 - Jan 2025',
|
||||||
|
contributors: 4,
|
||||||
|
files: 847,
|
||||||
|
});
|
||||||
|
|
||||||
|
output.patterns([
|
||||||
|
{
|
||||||
|
name: 'Conventional Commits',
|
||||||
|
trigger: 'when writing commit messages',
|
||||||
|
confidence: 0.85,
|
||||||
|
evidence: 'Found in 150/200 commits (feat:, fix:, refactor:)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Client/Server Component Split',
|
||||||
|
trigger: 'when creating Next.js pages',
|
||||||
|
confidence: 0.90,
|
||||||
|
evidence: 'Observed in markets/, premarkets/, portfolio/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Service Layer Architecture',
|
||||||
|
trigger: 'when adding backend logic',
|
||||||
|
confidence: 0.85,
|
||||||
|
evidence: 'Business logic in services/, not routes/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'TDD with E2E Tests',
|
||||||
|
trigger: 'when adding features',
|
||||||
|
confidence: 0.75,
|
||||||
|
evidence: '9 E2E test files, test(e2e) commits common',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
output.instincts([
|
||||||
|
{ name: 'pmx-conventional-commits', confidence: 0.85 },
|
||||||
|
{ name: 'pmx-client-component-pattern', confidence: 0.90 },
|
||||||
|
{ name: 'pmx-service-layer', confidence: 0.85 },
|
||||||
|
{ name: 'pmx-e2e-test-location', confidence: 0.90 },
|
||||||
|
{ name: 'pmx-package-manager', confidence: 0.95 },
|
||||||
|
{ name: 'pmx-hot-path-caution', confidence: 0.90 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
output.output(
|
||||||
|
'.claude/skills/pmx-patterns/SKILL.md',
|
||||||
|
'.claude/homunculus/instincts/inherited/pmx-instincts.yaml'
|
||||||
|
);
|
||||||
|
|
||||||
|
output.nextSteps();
|
||||||
|
output.footer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for use in other scripts
|
||||||
|
module.exports = { SkillCreateOutput, demo };
|
||||||
|
|
||||||
|
// Run demo if executed directly
|
||||||
|
if (require.main === module) {
|
||||||
|
demo().catch(console.error);
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ const assert = require('assert');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
const { execSync, spawn } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
// Test helper
|
// Test helper
|
||||||
function test(name, fn) {
|
function test(name, fn) {
|
||||||
@@ -113,14 +113,19 @@ async function runTests() {
|
|||||||
// Run the script
|
// Run the script
|
||||||
await runScript(path.join(scriptsDir, 'session-end.js'));
|
await runScript(path.join(scriptsDir, 'session-end.js'));
|
||||||
|
|
||||||
// Check if session file was created (default session ID)
|
// Check if session file was created
|
||||||
|
// Note: Without CLAUDE_SESSION_ID, falls back to project name (not 'default')
|
||||||
// Use local time to match the script's getDateString() function
|
// Use local time to match the script's getDateString() function
|
||||||
const sessionsDir = path.join(os.homedir(), '.claude', 'sessions');
|
const sessionsDir = path.join(os.homedir(), '.claude', 'sessions');
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
||||||
const sessionFile = path.join(sessionsDir, `${today}-default-session.tmp`);
|
|
||||||
|
|
||||||
assert.ok(fs.existsSync(sessionFile), 'Session file should exist');
|
// Get the expected session ID (project name fallback)
|
||||||
|
const utils = require('../../scripts/lib/utils');
|
||||||
|
const expectedId = utils.getSessionIdShort();
|
||||||
|
const sessionFile = path.join(sessionsDir, `${today}-${expectedId}-session.tmp`);
|
||||||
|
|
||||||
|
assert.ok(fs.existsSync(sessionFile), `Session file should exist: ${sessionFile}`);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (await asyncTest('includes session ID in filename', async () => {
|
if (await asyncTest('includes session ID in filename', async () => {
|
||||||
@@ -296,7 +301,7 @@ async function runTests() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [eventType, hookArray] of Object.entries(hooks.hooks)) {
|
for (const [, hookArray] of Object.entries(hooks.hooks)) {
|
||||||
checkHooks(hookArray);
|
checkHooks(hookArray);
|
||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
@@ -320,7 +325,7 @@ async function runTests() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [eventType, hookArray] of Object.entries(hooks.hooks)) {
|
for (const [, hookArray] of Object.entries(hooks.hooks)) {
|
||||||
checkHooks(hookArray);
|
checkHooks(hookArray);
|
||||||
}
|
}
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const os = require('os');
|
|||||||
|
|
||||||
// Import the modules
|
// Import the modules
|
||||||
const pm = require('../../scripts/lib/package-manager');
|
const pm = require('../../scripts/lib/package-manager');
|
||||||
const utils = require('../../scripts/lib/utils');
|
|
||||||
|
|
||||||
// Test helper
|
// Test helper
|
||||||
function test(name, fn) {
|
function test(name, fn) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const os = require('os');
|
|
||||||
|
|
||||||
// Import the module
|
// Import the module
|
||||||
const utils = require('../../scripts/lib/utils');
|
const utils = require('../../scripts/lib/utils');
|
||||||
|
|||||||
Reference in New Issue
Block a user