mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-30 06:42:43 +00:00
253 lines
8.4 KiB
TypeScript
253 lines
8.4 KiB
TypeScript
/**
|
|
* AI Context Generator
|
|
*
|
|
* Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
|
|
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
|
|
* CLAUDE.md is for Claude Code which only reads that file.
|
|
*/
|
|
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
// ESM equivalent of __dirname
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
interface RepoStats {
|
|
files?: number;
|
|
nodes?: number;
|
|
edges?: number;
|
|
communities?: number;
|
|
clusters?: number; // Aggregated cluster count (what tools show)
|
|
processes?: number;
|
|
}
|
|
|
|
const GITNEXUS_START_MARKER = '<!-- gitnexus:start -->';
|
|
const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
|
|
|
|
/**
|
|
* Generate the full GitNexus context content.
|
|
*
|
|
* Design principles (learned from real agent behavior):
|
|
* - AGENTS.md is the ROUTER — it tells the agent WHICH skill to read
|
|
* - Skills contain the actual workflows — AGENTS.md does NOT duplicate them
|
|
* - Bold **IMPORTANT** block + "Skills — Read First" heading — agents skip soft suggestions
|
|
* - One-line quick start (read context resource) gives agents an entry point
|
|
* - Tools/Resources sections are labeled "Reference" — agents treat them as lookup, not workflow
|
|
*/
|
|
function generateGitNexusContent(projectName: string, stats: RepoStats): string {
|
|
return `${GITNEXUS_START_MARKER}
|
|
# GitNexus MCP
|
|
|
|
This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows).
|
|
|
|
GitNexus provides a knowledge graph over this codebase — call chains, blast radius, execution flows, and semantic search.
|
|
|
|
## Always Start Here
|
|
|
|
For any task involving code understanding, debugging, impact analysis, or refactoring, you must:
|
|
|
|
1. **Read \`gitnexus://repo/{name}/context\`** — codebase overview + check index freshness
|
|
2. **Match your task to a skill below** and **read that skill file**
|
|
3. **Follow the skill's workflow and checklist**
|
|
|
|
> If step 1 warns the index is stale, run \`npx gitnexus analyze\` in the terminal first.
|
|
|
|
## Skills
|
|
|
|
| Task | Read this skill file |
|
|
|------|---------------------|
|
|
| Understand architecture / "How does X work?" | \`.claude/skills/gitnexus/exploring/SKILL.md\` |
|
|
| Blast radius / "What breaks if I change X?" | \`.claude/skills/gitnexus/impact-analysis/SKILL.md\` |
|
|
| Trace bugs / "Why is X failing?" | \`.claude/skills/gitnexus/debugging/SKILL.md\` |
|
|
| Rename / extract / split / refactor | \`.claude/skills/gitnexus/refactoring/SKILL.md\` |
|
|
|
|
## Tools Reference
|
|
|
|
| Tool | What it gives you |
|
|
|------|-------------------|
|
|
| \`query\` | Process-grouped code intelligence — execution flows related to a concept |
|
|
| \`context\` | 360-degree symbol view — categorized refs, processes it participates in |
|
|
| \`impact\` | Symbol blast radius — what breaks at depth 1/2/3 with confidence |
|
|
| \`detect_changes\` | Git-diff impact — what do your current changes affect |
|
|
| \`rename\` | Multi-file coordinated rename with confidence-tagged edits |
|
|
| \`cypher\` | Raw graph queries (read \`gitnexus://repo/{name}/schema\` first) |
|
|
| \`list_repos\` | Discover indexed repos |
|
|
|
|
## Resources Reference
|
|
|
|
Lightweight reads (~100-500 tokens) for navigation:
|
|
|
|
| Resource | Content |
|
|
|----------|---------|
|
|
| \`gitnexus://repo/{name}/context\` | Stats, staleness check |
|
|
| \`gitnexus://repo/{name}/clusters\` | All functional areas with cohesion scores |
|
|
| \`gitnexus://repo/{name}/cluster/{clusterName}\` | Area members |
|
|
| \`gitnexus://repo/{name}/processes\` | All execution flows |
|
|
| \`gitnexus://repo/{name}/process/{processName}\` | Step-by-step trace |
|
|
| \`gitnexus://repo/{name}/schema\` | Graph schema for Cypher |
|
|
|
|
## Graph Schema
|
|
|
|
**Nodes:** File, Function, Class, Interface, Method, Community, Process
|
|
**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS
|
|
|
|
\`\`\`cypher
|
|
MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"})
|
|
RETURN caller.name, caller.filePath
|
|
\`\`\`
|
|
|
|
${GITNEXUS_END_MARKER}`;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if a file exists
|
|
*/
|
|
async function fileExists(filePath: string): Promise<boolean> {
|
|
try {
|
|
await fs.access(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create or update GitNexus section in a file
|
|
* - If file doesn't exist: create with GitNexus content
|
|
* - If file exists without GitNexus section: append
|
|
* - If file exists with GitNexus section: replace that section
|
|
*/
|
|
async function upsertGitNexusSection(
|
|
filePath: string,
|
|
content: string
|
|
): Promise<'created' | 'updated' | 'appended'> {
|
|
const exists = await fileExists(filePath);
|
|
|
|
if (!exists) {
|
|
await fs.writeFile(filePath, content, 'utf-8');
|
|
return 'created';
|
|
}
|
|
|
|
const existingContent = await fs.readFile(filePath, 'utf-8');
|
|
|
|
// Check if GitNexus section already exists
|
|
const startIdx = existingContent.indexOf(GITNEXUS_START_MARKER);
|
|
const endIdx = existingContent.indexOf(GITNEXUS_END_MARKER);
|
|
|
|
if (startIdx !== -1 && endIdx !== -1) {
|
|
// Replace existing section
|
|
const before = existingContent.substring(0, startIdx);
|
|
const after = existingContent.substring(endIdx + GITNEXUS_END_MARKER.length);
|
|
const newContent = before + content + after;
|
|
await fs.writeFile(filePath, newContent.trim() + '\n', 'utf-8');
|
|
return 'updated';
|
|
}
|
|
|
|
// Append new section
|
|
const newContent = existingContent.trim() + '\n\n' + content + '\n';
|
|
await fs.writeFile(filePath, newContent, 'utf-8');
|
|
return 'appended';
|
|
}
|
|
|
|
/**
|
|
* Install GitNexus skills to .claude/skills/gitnexus/
|
|
* Works natively with Claude Code, Cursor, and GitHub Copilot
|
|
*/
|
|
async function installSkills(repoPath: string): Promise<string[]> {
|
|
const skillsDir = path.join(repoPath, '.claude', 'skills', 'gitnexus');
|
|
const installedSkills: string[] = [];
|
|
|
|
// Skill definitions bundled with the package
|
|
const skills = [
|
|
{
|
|
name: 'exploring',
|
|
description: 'Navigate unfamiliar code using GitNexus knowledge graph',
|
|
},
|
|
{
|
|
name: 'debugging',
|
|
description: 'Trace bugs through call chains using knowledge graph',
|
|
},
|
|
{
|
|
name: 'impact-analysis',
|
|
description: 'Analyze blast radius before making code changes',
|
|
},
|
|
{
|
|
name: 'refactoring',
|
|
description: 'Plan safe refactors using blast radius and dependency mapping',
|
|
},
|
|
];
|
|
|
|
for (const skill of skills) {
|
|
const skillDir = path.join(skillsDir, skill.name);
|
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
|
|
try {
|
|
// Create skill directory
|
|
await fs.mkdir(skillDir, { recursive: true });
|
|
|
|
// Try to read from package skills directory
|
|
const packageSkillPath = path.join(__dirname, '..', '..', 'skills', `${skill.name}.md`);
|
|
let skillContent: string;
|
|
|
|
try {
|
|
skillContent = await fs.readFile(packageSkillPath, 'utf-8');
|
|
} catch {
|
|
// Fallback: generate minimal skill content
|
|
skillContent = `---
|
|
name: gitnexus-${skill.name}
|
|
description: ${skill.description}
|
|
---
|
|
|
|
# ${skill.name.charAt(0).toUpperCase() + skill.name.slice(1)}
|
|
|
|
${skill.description}
|
|
|
|
Use GitNexus tools to accomplish this task.
|
|
`;
|
|
}
|
|
|
|
await fs.writeFile(skillPath, skillContent, 'utf-8');
|
|
installedSkills.push(skill.name);
|
|
} catch (err) {
|
|
// Skip on error, don't fail the whole process
|
|
console.warn(`Warning: Could not install skill ${skill.name}:`, err);
|
|
}
|
|
}
|
|
|
|
return installedSkills;
|
|
}
|
|
|
|
/**
|
|
* Generate AI context files after indexing
|
|
*/
|
|
export async function generateAIContextFiles(
|
|
repoPath: string,
|
|
_storagePath: string,
|
|
projectName: string,
|
|
stats: RepoStats
|
|
): Promise<{ files: string[] }> {
|
|
const content = generateGitNexusContent(projectName, stats);
|
|
const createdFiles: string[] = [];
|
|
|
|
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
|
|
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
|
const agentsResult = await upsertGitNexusSection(agentsPath, content);
|
|
createdFiles.push(`AGENTS.md (${agentsResult})`);
|
|
|
|
// Create CLAUDE.md (for Claude Code)
|
|
const claudePath = path.join(repoPath, 'CLAUDE.md');
|
|
const claudeResult = await upsertGitNexusSection(claudePath, content);
|
|
createdFiles.push(`CLAUDE.md (${claudeResult})`);
|
|
|
|
// Install skills to .claude/skills/gitnexus/
|
|
const installedSkills = await installSkills(repoPath);
|
|
if (installedSkills.length > 0) {
|
|
createdFiles.push(`.claude/skills/gitnexus/ (${installedSkills.length} skills)`);
|
|
}
|
|
|
|
return { files: createdFiles };
|
|
}
|
|
|