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
151 lines • 5.42 kB
JavaScript
/**
* Gitignore Helper
*
* Shared module for checking and updating .gitignore files with recommended
* AIWG runtime and provider directory patterns.
*
* @implements #553
* @module src/config/gitignore
*/
import * as fs from 'fs/promises';
import * as path from 'path';
// ===========================
// Recommended Patterns
// ===========================
/**
* AIWG runtime subdirectories that should be gitignored.
* NOTE: .aiwg/ itself is intentionally NOT included — it contains
* project artifacts (requirements, architecture, etc.) that should be versioned.
* Only the high-churn runtime subdirectories are ignored.
*/
export const AIWG_RUNTIME_PATTERNS = [
'.aiwg/working/',
'.aiwg/ralph/',
'.aiwg/ralph-external/',
];
/**
* Provider conventional directories for non-native deployments.
* These are generated by `aiwg use` and should not be version-controlled.
* Claude Code native dirs (.claude/agents/, .claude/skills/, etc.) are intentionally
* excluded from this list — they are authored artifacts worth versioning.
*/
export const PROVIDER_CONVENTIONAL_PATTERNS = [
'.codex/',
'.cursor/agents/',
'.cursor/commands/',
'.cursor/skills/',
'.factory/skills/',
'.factory/rules/',
'.warp/',
'.windsurf/agents/',
'.windsurf/skills/',
'.windsurf/rules/',
];
/**
* Claude Code session state patterns (machine-local, should not be versioned).
*/
export const CLAUDE_SESSION_PATTERNS = [
'.claude/settings.local.json',
];
/** All recommended patterns combined */
export const ALL_RECOMMENDED_PATTERNS = [
...AIWG_RUNTIME_PATTERNS,
...CLAUDE_SESSION_PATTERNS,
...PROVIDER_CONVENTIONAL_PATTERNS,
];
// ===========================
// Core Functions
// ===========================
/**
* Check which recommended patterns are missing from the project's .gitignore.
*/
export async function checkGitignore(projectRoot) {
const gitignorePath = path.join(projectRoot, '.gitignore');
let content = '';
let exists = false;
try {
content = await fs.readFile(gitignorePath, 'utf-8');
exists = true;
}
catch {
// File doesn't exist — all patterns are missing
}
const lines = content.split('\n').map(l => l.trim());
const isCovered = (pattern) => {
// Exact match
if (lines.includes(pattern))
return true;
// Pattern without trailing slash is also sufficient
if (lines.includes(pattern.replace(/\/$/, '')))
return true;
// Parent directory is ignored (covers all children)
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;
};
const missingRuntime = AIWG_RUNTIME_PATTERNS.filter(p => !isCovered(p));
const missingSession = CLAUDE_SESSION_PATTERNS.filter(p => !isCovered(p));
const missingProvider = PROVIDER_CONVENTIONAL_PATTERNS.filter(p => !isCovered(p));
const missing = [...missingRuntime, ...missingSession, ...missingProvider];
return { exists, missing, missingRuntime, missingSession, missingProvider };
}
/**
* Append missing patterns to .gitignore, creating the file if it doesn't exist.
* Patterns are grouped with comments.
*/
export async function appendGitignore(projectRoot, patterns) {
const gitignorePath = path.join(projectRoot, '.gitignore');
let existing = '';
let fileExists = false;
try {
existing = await fs.readFile(gitignorePath, 'utf-8');
fileExists = true;
}
catch {
// File doesn't exist, will create
}
const existingLines = existing.split('\n').map(l => l.trim());
const toAdd = [];
const alreadyPresent = [];
for (const pattern of patterns) {
if (existingLines.includes(pattern) || existingLines.includes(pattern.replace(/\/$/, ''))) {
alreadyPresent.push(pattern);
}
else {
toAdd.push(pattern);
}
}
if (toAdd.length > 0) {
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 newContent = existing.endsWith('\n') || existing === ''
? existing + block
: existing + '\n' + block;
await fs.writeFile(gitignorePath, newContent, 'utf-8');
}
return {
added: toAdd,
alreadyPresent,
created: !fileExists && toAdd.length > 0,
};
}
//# sourceMappingURL=gitignore.js.map