ccshare
Version:
Share Claude Code prompts and results easily
305 lines • 12.7 kB
JavaScript
import fs from 'fs/promises';
import path from 'path';
import { execSync } from 'child_process';
export async function analyzeProject() {
const projectPath = process.cwd();
const projectName = path.basename(projectPath);
const info = {
timestamp: new Date().toISOString(),
projectPath,
projectName,
prompts: {
count: 0,
recent: []
},
changes: {
files: [],
summary: ''
},
claudeConfig: {
exists: false
},
projectSummary: {
language: 'unknown',
description: ''
}
};
// Check for CLAUDE.md
try {
const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
const claudeMdContent = await fs.readFile(claudeMdPath, 'utf-8');
info.claudeConfig.exists = true;
info.claudeConfig.content = parseClaudeMd(claudeMdContent);
}
catch {
// No CLAUDE.md file
}
// Scan for Claude session history
await scanSessionHistory(info);
// Get git history and file changes
try {
// Get recent file changes with diffs
const gitStatus = execSync('git status --porcelain', { encoding: 'utf-8' });
const changes = gitStatus.trim().split('\n').filter(line => line);
for (const change of changes) {
const [status, ...fileParts] = change.trim().split(/\s+/);
const filePath = fileParts.join(' ');
let type;
if (status === 'A' || status === '??')
type = 'created';
else if (status === 'M')
type = 'modified';
else if (status === 'D')
type = 'deleted';
else
continue;
const fileChange = {
path: filePath,
type
};
// Get diff and code changes for modified files
if (type === 'modified') {
try {
const diff = execSync(`git diff --unified=10 "${filePath}"`, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore']
});
// Parse diff to extract code changes
const lines = diff.split('\n');
let additions = 0, deletions = 0;
const codeChanges = [];
let currentLine = 0;
lines.forEach(line => {
if (line.startsWith('@@')) {
// Parse line numbers from diff header
const match = line.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/);
if (match) {
currentLine = parseInt(match[2]);
}
}
else if (line.startsWith('+') && !line.startsWith('+++')) {
additions++;
codeChanges.push({
lineNumber: currentLine++,
type: 'added',
content: line.substring(1)
});
}
else if (line.startsWith('-') && !line.startsWith('---')) {
deletions++;
codeChanges.push({
lineNumber: currentLine,
type: 'removed',
content: line.substring(1)
});
}
else if (!line.startsWith('\\') && line.length > 0 && !line.startsWith('diff') && !line.startsWith('index')) {
// Context line
if (codeChanges.length > 0) { // Remove limit
codeChanges.push({
lineNumber: currentLine++,
type: 'context',
content: line.substring(1)
});
}
else {
currentLine++;
}
}
});
fileChange.additions = additions;
fileChange.deletions = deletions;
fileChange.diff = diff; // Full diff without truncation
fileChange.codeChanges = codeChanges; // All code changes
// Get before/after content for modified files
try {
// Current content (after)
const afterCode = await fs.readFile(path.join(info.projectPath, filePath), 'utf-8');
fileChange.afterCode = afterCode; // Full content
// Original content (before) - from git
try {
const beforeCode = execSync(`git show HEAD:"${filePath}"`, {
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'ignore']
});
fileChange.beforeCode = beforeCode; // Full content
}
catch {
// File might be new in working directory
}
}
catch { }
}
catch { }
}
// For new files, show the content
if (type === 'created') {
try {
const content = await fs.readFile(path.join(info.projectPath, filePath), 'utf-8');
fileChange.afterCode = content; // Full content
// Count lines for new files
const lines = content.split('\n');
fileChange.additions = lines.length;
// Show all lines as code changes
fileChange.codeChanges = lines.map((line, index) => ({
lineNumber: index + 1,
type: 'added',
content: line
}));
}
catch { }
}
info.changes.files.push(fileChange);
}
const totalChanges = info.changes.files.length;
info.changes.summary = `${totalChanges} file(s) changed`;
// Create simplified fileDiffs array for output
info.fileDiffs = info.changes.files
.filter(f => f.diff)
.map(f => ({
path: f.path,
diff: f.diff || ''
}));
}
catch {
// Not a git repo or git not available
info.changes.summary = 'Git history not available';
info.fileDiffs = [];
}
// Analyze project type
await analyzeProjectType(info);
return info;
}
async function analyzeProjectType(info) {
const projectPath = info.projectPath;
try {
// Check for package.json
const packageJsonPath = path.join(projectPath, 'package.json');
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
info.projectSummary.language = 'JavaScript/TypeScript';
// Detect framework
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
if (deps.react)
info.projectSummary.framework = 'React';
else if (deps.vue)
info.projectSummary.framework = 'Vue';
else if (deps.angular)
info.projectSummary.framework = 'Angular';
else if (deps.express)
info.projectSummary.framework = 'Express';
else if (deps.next)
info.projectSummary.framework = 'Next.js';
info.projectSummary.description = packageJson.description || 'Node.js project';
return;
}
catch { }
try {
// Check for requirements.txt or setup.py
await fs.access(path.join(projectPath, 'requirements.txt'));
info.projectSummary.language = 'Python';
info.projectSummary.description = 'Python project';
return;
}
catch { }
try {
// Check for Cargo.toml
const cargoToml = await fs.readFile(path.join(projectPath, 'Cargo.toml'), 'utf-8');
info.projectSummary.language = 'Rust';
info.projectSummary.description = 'Rust project';
return;
}
catch { }
// Default
info.projectSummary.description = 'General project';
}
function parseClaudeMd(content) {
const config = {
instructions: [],
preferences: {}
};
const lines = content.split('\n');
let currentSection = '';
for (const line of lines) {
if (line.startsWith('# ')) {
currentSection = line.substring(2).toLowerCase();
}
else if (line.trim() && currentSection === 'instructions') {
config.instructions.push(line.trim());
}
else if (line.includes(':') && currentSection === 'preferences') {
const [key, value] = line.split(':').map(s => s.trim());
if (key && value) {
config.preferences[key] = value;
}
}
}
return config;
}
async function scanSessionHistory(info) {
// Look for Claude session files in common locations
const sessionPaths = [
path.join(process.env.HOME || '', '.claude', 'sessions'),
path.join(process.env.HOME || '', '.claude-code', 'sessions'),
path.join(info.projectPath, '.claude'),
path.join(info.projectPath, '.claude-sessions')
];
const allPrompts = [];
const sessionFiles = [];
for (const sessionPath of sessionPaths) {
try {
const files = await fs.readdir(sessionPath);
const jsonFiles = files.filter(f => f.endsWith('.json'));
for (const file of jsonFiles) {
try {
const filePath = path.join(sessionPath, file);
const content = await fs.readFile(filePath, 'utf-8');
const sessionData = JSON.parse(content);
// Extract prompts from session
if (sessionData.messages && Array.isArray(sessionData.messages)) {
for (let i = 0; i < sessionData.messages.length; i++) {
const msg = sessionData.messages[i];
if (msg.role === 'user') {
const prompt = {
content: msg.content.substring(0, 200) + (msg.content.length > 200 ? '...' : ''),
timestamp: msg.timestamp
};
// Find the next assistant response
if (i + 1 < sessionData.messages.length && sessionData.messages[i + 1].role === 'assistant') {
prompt.response = sessionData.messages[i + 1].content.substring(0, 100) + '...';
}
allPrompts.push(prompt);
}
}
}
// Also check for conversation format
if (sessionData.conversation) {
const conversation = sessionData.conversation;
const userPrompts = conversation.match(/(?:Human:|User:)\s*([^\n]+)/g);
if (userPrompts) {
userPrompts.forEach((prompt) => {
allPrompts.push({
content: prompt.replace(/^(Human:|User:)\s*/, '').substring(0, 200)
});
});
}
}
sessionFiles.push(file);
}
catch { }
}
}
catch { }
}
// Update info with found prompts
info.prompts.count = allPrompts.length;
info.prompts.recent = allPrompts.slice(-5).reverse(); // Last 5, most recent first
// Add session history summary
if (sessionFiles.length > 0) {
info.sessionHistory = [{
timestamp: new Date().toISOString(),
prompts: allPrompts.length,
filesChanged: info.changes.files.length
}];
}
}
//# sourceMappingURL=analyze.js.map