UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

198 lines (168 loc) 6.15 kB
#!/usr/bin/env node /** * aiwg config gitignore * * Show, check, or fix .gitignore entries for AIWG runtime dirs * and provider conventional directories. * * Usage: * aiwg config gitignore # Show recommended entries and current status * aiwg config gitignore --fix # Append missing entries to .gitignore * aiwg config gitignore --check # Exit non-zero if any entries are missing (CI use) * * @implements #553 */ import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; const _scriptDir = path.dirname(fileURLToPath(import.meta.url)); // =========================== // Recommended Patterns (inline to avoid TS import complexity from .mjs) // =========================== const AIWG_RUNTIME_PATTERNS = [ '.aiwg/working/', '.aiwg/ralph/', '.aiwg/ralph-external/', ]; const CLAUDE_SESSION_PATTERNS = [ '.claude/settings.local.json', ]; const PROVIDER_CONVENTIONAL_PATTERNS = [ '.codex/', '.cursor/agents/', '.cursor/commands/', '.cursor/skills/', '.factory/skills/', '.factory/rules/', '.warp/', '.windsurf/agents/', '.windsurf/skills/', '.windsurf/rules/', ]; const ALL_PATTERNS = [ ...AIWG_RUNTIME_PATTERNS, ...CLAUDE_SESSION_PATTERNS, ...PROVIDER_CONVENTIONAL_PATTERNS, ]; // =========================== // Helpers // =========================== function isCovered(pattern, lines) { if (lines.includes(pattern)) return true; if (lines.includes(pattern.replace(/\/$/, ''))) return true; const parts = pattern.split('/').filter(Boolean); for (let i = 1; i < parts.length; i++) { const parent = parts.slice(0, i).join('/') + '/'; if (lines.includes(parent) || lines.includes(parent.replace(/\/$/, ''))) { return true; } } return false; } async function readGitignore(projectRoot) { const p = path.join(projectRoot, '.gitignore'); try { const content = await fs.readFile(p, 'utf-8'); return { exists: true, content, lines: content.split('\n').map(l => l.trim()) }; } catch { return { exists: false, content: '', lines: [] }; } } async function appendPatterns(projectRoot, patterns, existing) { const gitignorePath = path.join(projectRoot, '.gitignore'); const toAdd = patterns.filter(p => !isCovered(p, existing.lines)); if (toAdd.length === 0) return { added: [], created: false }; const runtimeToAdd = toAdd.filter(p => AIWG_RUNTIME_PATTERNS.includes(p)); const sessionToAdd = toAdd.filter(p => CLAUDE_SESSION_PATTERNS.includes(p)); const providerToAdd = toAdd.filter(p => PROVIDER_CONVENTIONAL_PATTERNS.includes(p)); let block = ''; if (runtimeToAdd.length > 0) { block += '\n# AIWG — ignore only high-churn runtime subdirs, NOT .aiwg/ itself\n'; block += runtimeToAdd.join('\n') + '\n'; } if (sessionToAdd.length > 0) { block += '\n# Claude Code local session state\n'; block += sessionToAdd.join('\n') + '\n'; } if (providerToAdd.length > 0) { block += '\n# Agentic provider conventional dirs (generated by aiwg use, not authored)\n'; block += providerToAdd.join('\n') + '\n'; } const content = existing.content.endsWith('\n') || existing.content === '' ? existing.content + block : existing.content + '\n' + block; await fs.writeFile(gitignorePath, content, 'utf-8'); return { added: toAdd, created: !existing.exists }; } // =========================== // Main // =========================== async function main(args) { const fix = args.includes('--fix'); const check = args.includes('--check'); const projectRoot = process.cwd(); const { exists, lines } = await readGitignore(projectRoot); const existing = await readGitignore(projectRoot); const missingRuntime = AIWG_RUNTIME_PATTERNS.filter(p => !isCovered(p, lines)); const missingSession = CLAUDE_SESSION_PATTERNS.filter(p => !isCovered(p, lines)); const missingProvider = PROVIDER_CONVENTIONAL_PATTERNS.filter(p => !isCovered(p, lines)); const missing = [...missingRuntime, ...missingSession, ...missingProvider]; if (check) { // CI mode: only fail on missing runtime paths (the critical ones) if (missingRuntime.length > 0) { console.error('✗ Missing AIWG runtime gitignore entries:'); missingRuntime.forEach(p => console.error(` ${p}`)); console.error('\nRun: aiwg config gitignore --fix'); process.exit(1); } console.log('✓ AIWG runtime paths are gitignored'); process.exit(0); } if (fix) { const result = await appendPatterns(projectRoot, missing, existing); if (result.added.length === 0) { console.log('✓ .gitignore is already up to date'); } else { console.log(result.created ? 'Created .gitignore with AIWG entries:' : 'Updated .gitignore:'); result.added.forEach(p => console.log(` + ${p}`)); } return; } // Default: show status if (!exists) { console.log('No .gitignore found in current directory.'); console.log(''); } console.log('AIWG .gitignore recommendations:'); console.log(''); console.log('AIWG Runtime (high-churn subdirs — these should always be ignored):'); AIWG_RUNTIME_PATTERNS.forEach(p => { const ok = isCovered(p, lines); console.log(` ${ok ? '✓' : '⚠'} ${p}${ok ? '' : ' ← missing'}`); }); console.log(''); console.log('Claude Code session state:'); CLAUDE_SESSION_PATTERNS.forEach(p => { const ok = isCovered(p, lines); console.log(` ${ok ? '✓' : '⚠'} ${p}${ok ? '' : ' ← missing'}`); }); console.log(''); console.log('Provider conventional dirs (generated by aiwg use):'); PROVIDER_CONVENTIONAL_PATTERNS.forEach(p => { const ok = isCovered(p, lines); console.log(` ${ok ? '✓' : '○'} ${p}${ok ? '' : ' ← not present'}`); }); console.log(''); if (missing.length === 0) { console.log('✓ .gitignore is up to date'); } else { console.log(`⚠ ${missing.length} recommended entries missing`); console.log(''); console.log('Run: aiwg config gitignore --fix'); } } const args = process.argv.slice(2); main(args).catch(err => { console.error('Error:', err.message); process.exit(1); });