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
JavaScript
/**
* 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);
});