From a44a0553bb586fdc52208bb47223e16b180a6cac Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Thu, 29 Jan 2026 02:58:51 -0800 Subject: [PATCH] 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. --- .gitignore | 3 + scripts/hooks/check-console-log.js | 2 +- scripts/hooks/session-end.js | 1 - scripts/hooks/session-start.js | 1 - scripts/hooks/suggest-compact.js | 1 - scripts/lib/package-manager.js | 2 +- scripts/lib/utils.js | 2 +- scripts/setup-package-manager.js | 4 +- scripts/skill-create-output.js | 244 +++++++++++++++++++++++++++++ tests/hooks/hooks.test.js | 17 +- tests/lib/package-manager.test.js | 1 - tests/lib/utils.test.js | 1 - 12 files changed, 262 insertions(+), 17 deletions(-) create mode 100644 scripts/skill-create-output.js diff --git a/.gitignore b/.gitignore index d947b2c8..31ba5f1d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ private/ # Session templates (not committed) examples/sessions/*.tmp + +# Local drafts +marketing/ diff --git a/scripts/hooks/check-console-log.js b/scripts/hooks/check-console-log.js index e1aa3c24..9b25fc4e 100755 --- a/scripts/hooks/check-console-log.js +++ b/scripts/hooks/check-console-log.js @@ -52,7 +52,7 @@ process.stdin.on('end', () => { if (hasConsole) { console.error('[Hook] Remove console.log statements before committing'); } - } catch (error) { + } catch (_error) { // Silently ignore errors (git might not be available, etc.) } diff --git a/scripts/hooks/session-end.js b/scripts/hooks/session-end.js index cfc389d7..b429db37 100644 --- a/scripts/hooks/session-end.js +++ b/scripts/hooks/session-end.js @@ -16,7 +16,6 @@ const { getTimeString, getSessionIdShort, ensureDir, - readFile, writeFile, replaceInFile, log diff --git a/scripts/hooks/session-start.js b/scripts/hooks/session-start.js index 5e38231a..76fa6007 100644 --- a/scripts/hooks/session-start.js +++ b/scripts/hooks/session-start.js @@ -8,7 +8,6 @@ * files and notifies Claude of available context to load. */ -const path = require('path'); const { getSessionsDir, getLearnedSkillsDir, diff --git a/scripts/hooks/suggest-compact.js b/scripts/hooks/suggest-compact.js index ae690b78..323e34c9 100644 --- a/scripts/hooks/suggest-compact.js +++ b/scripts/hooks/suggest-compact.js @@ -14,7 +14,6 @@ */ const path = require('path'); -const fs = require('fs'); const { getTempDir, readFile, diff --git a/scripts/lib/package-manager.js b/scripts/lib/package-manager.js index 0b950568..cd8f6ee4 100644 --- a/scripts/lib/package-manager.js +++ b/scripts/lib/package-manager.js @@ -7,7 +7,7 @@ const fs = require('fs'); const path = require('path'); -const { commandExists, getClaudeDir, readFile, writeFile, log, runCommand } = require('./utils'); +const { commandExists, getClaudeDir, readFile, writeFile } = require('./utils'); // Package manager definitions const PACKAGE_MANAGERS = { diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index ee7f785d..1fa46162 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -165,7 +165,7 @@ function findFiles(dir, pattern, options = {}) { searchDir(fullPath); } } - } catch (err) { + } catch (_err) { // Ignore permission errors } } diff --git a/scripts/setup-package-manager.js b/scripts/setup-package-manager.js index 1a6a8fab..bdc833be 100644 --- a/scripts/setup-package-manager.js +++ b/scripts/setup-package-manager.js @@ -19,10 +19,8 @@ const { setProjectPackageManager, getAvailablePackageManagers, detectFromLockFile, - detectFromPackageJson, - getSelectionPrompt + detectFromPackageJson } = require('./lib/package-manager'); -const { log } = require('./lib/utils'); function showHelp() { console.log(` diff --git a/scripts/skill-create-output.js b/scripts/skill-create-output.js new file mode 100644 index 00000000..27412608 --- /dev/null +++ b/scripts/skill-create-output.js @@ -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 ')} +${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); +} diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 2ed292ad..8994d406 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -8,7 +8,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('fs'); const os = require('os'); -const { execSync, spawn } = require('child_process'); +const { spawn } = require('child_process'); // Test helper function test(name, fn) { @@ -113,14 +113,19 @@ async function runTests() { // Run the script 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 const sessionsDir = path.join(os.homedir(), '.claude', 'sessions'); const now = new Date(); 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++; 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); } })) 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); } })) passed++; else failed++; diff --git a/tests/lib/package-manager.test.js b/tests/lib/package-manager.test.js index 75df3bc7..fb496c0d 100644 --- a/tests/lib/package-manager.test.js +++ b/tests/lib/package-manager.test.js @@ -11,7 +11,6 @@ const os = require('os'); // Import the modules const pm = require('../../scripts/lib/package-manager'); -const utils = require('../../scripts/lib/utils'); // Test helper function test(name, fn) { diff --git a/tests/lib/utils.test.js b/tests/lib/utils.test.js index 96b0fde9..208ef15c 100644 --- a/tests/lib/utils.test.js +++ b/tests/lib/utils.test.js @@ -7,7 +7,6 @@ const assert = require('assert'); const path = require('path'); const fs = require('fs'); -const os = require('os'); // Import the module const utils = require('../../scripts/lib/utils');