codeep 1.0.125 → 1.0.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.js +24 -0
- package/dist/app.js +127 -2
- package/dist/components/AgentProgress.d.ts +5 -5
- package/dist/components/AgentProgress.js +110 -54
- package/dist/components/MessageList.d.ts +1 -0
- package/dist/components/MessageList.js +2 -2
- package/dist/components/ProjectPermission.js +17 -4
- package/dist/config/index.d.ts +13 -0
- package/dist/config/index.js +53 -0
- package/dist/utils/projectIntelligence.d.ts +67 -0
- package/dist/utils/projectIntelligence.js +601 -0
- package/package.json +1 -1
package/dist/api/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { config, getApiKey } from '../config/index.js';
|
|
|
2
2
|
import { withRetry, isNetworkError, isTimeoutError } from '../utils/retry.js';
|
|
3
3
|
import { getProvider, getProviderBaseUrl, getProviderAuthHeader } from '../config/providers.js';
|
|
4
4
|
import { logApiRequest, logApiResponse } from '../utils/logger.js';
|
|
5
|
+
import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
|
|
5
6
|
// Error messages by language
|
|
6
7
|
const ERROR_MESSAGES = {
|
|
7
8
|
en: {
|
|
@@ -24,8 +25,16 @@ function getErrorMessage(key) {
|
|
|
24
25
|
}
|
|
25
26
|
// Store project context for use in system prompt
|
|
26
27
|
let currentProjectContext = null;
|
|
28
|
+
let cachedIntelligence = null;
|
|
27
29
|
export function setProjectContext(ctx) {
|
|
28
30
|
currentProjectContext = ctx;
|
|
31
|
+
// Try to load cached intelligence when project context is set
|
|
32
|
+
if (ctx) {
|
|
33
|
+
cachedIntelligence = loadProjectIntelligence(ctx.root);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
cachedIntelligence = null;
|
|
37
|
+
}
|
|
29
38
|
}
|
|
30
39
|
export async function chat(message, history = [], onChunk, onRetry, projectContext, abortSignal) {
|
|
31
40
|
// Update project context if provided
|
|
@@ -132,6 +141,21 @@ The user will review and approve changes before they are applied.`
|
|
|
132
141
|
: `
|
|
133
142
|
|
|
134
143
|
**Write Access:** READ-ONLY - You can analyze code but cannot suggest file modifications.`;
|
|
144
|
+
// Use cached intelligence if available (from /scan command)
|
|
145
|
+
// This provides richer context than basic project structure
|
|
146
|
+
if (cachedIntelligence) {
|
|
147
|
+
const intelligenceContext = generateContextFromIntelligence(cachedIntelligence);
|
|
148
|
+
const projectInfo = `
|
|
149
|
+
|
|
150
|
+
## Project Intelligence (cached)
|
|
151
|
+
${intelligenceContext}
|
|
152
|
+
${writeInfo}
|
|
153
|
+
|
|
154
|
+
When the user mentions a file path, the file content will be automatically attached to their message.
|
|
155
|
+
You can analyze, explain, or suggest improvements to the code.`;
|
|
156
|
+
return basePrompt + projectInfo;
|
|
157
|
+
}
|
|
158
|
+
// Fallback to basic project context
|
|
135
159
|
const projectInfo = `
|
|
136
160
|
|
|
137
161
|
## Project Context
|
package/dist/app.js
CHANGED
|
@@ -35,8 +35,8 @@ import { saveContext, loadContext, clearContext, mergeContext } from './utils/co
|
|
|
35
35
|
import { performCodeReview, formatReviewResult } from './utils/codeReview.js';
|
|
36
36
|
import { learnFromProject, addCustomRule, getLearningStatus } from './utils/learning.js';
|
|
37
37
|
import { getAllSkills, findSkill, formatSkillsList, formatSkillHelp, generateSkillPrompt, saveCustomSkill, deleteCustomSkill, parseSkillChain, parseSkillArgs, searchSkills, trackSkillUsage, getSkillStats } from './utils/skills.js';
|
|
38
|
-
import { AgentProgress, LiveCodeStream } from './components/AgentProgress.js';
|
|
39
38
|
import { createActionLog } from './utils/tools.js';
|
|
39
|
+
import { scanProject, saveProjectIntelligence, loadProjectIntelligence, generateContextFromIntelligence } from './utils/projectIntelligence.js';
|
|
40
40
|
export const App = () => {
|
|
41
41
|
const { exit } = useApp();
|
|
42
42
|
const { stdout } = useStdout();
|
|
@@ -82,6 +82,7 @@ export const App = () => {
|
|
|
82
82
|
const [agentThinking, setAgentThinking] = useState('');
|
|
83
83
|
const [agentResult, setAgentResult] = useState(null);
|
|
84
84
|
const [agentDryRun, setAgentDryRun] = useState(false);
|
|
85
|
+
const [agentStreamingContent, setAgentStreamingContent] = useState(''); // Live action log in chat
|
|
85
86
|
// Load API keys for ALL providers on startup and check if current provider is configured
|
|
86
87
|
useEffect(() => {
|
|
87
88
|
loadAllApiKeys().then(() => {
|
|
@@ -266,6 +267,7 @@ export const App = () => {
|
|
|
266
267
|
setAgentThinking('');
|
|
267
268
|
setAgentResult(null);
|
|
268
269
|
setAgentDryRun(dryRun);
|
|
270
|
+
setAgentStreamingContent(''); // Reset streaming content
|
|
269
271
|
// Add user message
|
|
270
272
|
const userMessage = {
|
|
271
273
|
role: 'user',
|
|
@@ -322,6 +324,78 @@ export const App = () => {
|
|
|
322
324
|
}
|
|
323
325
|
return updated;
|
|
324
326
|
});
|
|
327
|
+
// Add formatted action to streaming content (stays in chat)
|
|
328
|
+
const actionType = actionLog.type;
|
|
329
|
+
const target = actionLog.target.split('/').pop() || actionLog.target; // Just filename
|
|
330
|
+
const fullPath = actionLog.target;
|
|
331
|
+
const status = actionLog.result === 'success' ? '✓' : '✗';
|
|
332
|
+
const statusColor = actionLog.result === 'success' ? '' : ' (failed)';
|
|
333
|
+
// Get file extension for syntax highlighting
|
|
334
|
+
const getLanguage = (filename) => {
|
|
335
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
336
|
+
const langMap = {
|
|
337
|
+
'ts': 'typescript', 'tsx': 'typescript', 'js': 'javascript', 'jsx': 'javascript',
|
|
338
|
+
'py': 'python', 'rb': 'ruby', 'go': 'go', 'rs': 'rust', 'java': 'java',
|
|
339
|
+
'php': 'php', 'css': 'css', 'scss': 'css', 'html': 'html', 'vue': 'html',
|
|
340
|
+
'json': 'json', 'yml': 'yaml', 'yaml': 'yaml', 'md': 'markdown',
|
|
341
|
+
'sh': 'bash', 'bash': 'bash', 'sql': 'sql',
|
|
342
|
+
};
|
|
343
|
+
return langMap[ext] || ext || 'code';
|
|
344
|
+
};
|
|
345
|
+
let actionLine = '';
|
|
346
|
+
if (actionType === 'write' && actionLog.details) {
|
|
347
|
+
// Show full code with syntax highlighting
|
|
348
|
+
const lang = getLanguage(target);
|
|
349
|
+
actionLine = `${status} **Created ${fullPath}**\n\`\`\`${lang}\n${actionLog.details}\n\`\`\``;
|
|
350
|
+
}
|
|
351
|
+
else if (actionType === 'write') {
|
|
352
|
+
actionLine = `${status} Created **${target}**${statusColor}`;
|
|
353
|
+
}
|
|
354
|
+
else if (actionType === 'edit' && actionLog.details) {
|
|
355
|
+
// Show edited code with syntax highlighting
|
|
356
|
+
const lang = getLanguage(target);
|
|
357
|
+
actionLine = `${status} **Edited ${fullPath}**\n\`\`\`${lang}\n${actionLog.details}\n\`\`\``;
|
|
358
|
+
}
|
|
359
|
+
else if (actionType === 'edit') {
|
|
360
|
+
actionLine = `${status} Edited **${target}**${statusColor}`;
|
|
361
|
+
}
|
|
362
|
+
else if (actionType === 'read') {
|
|
363
|
+
// Don't show read actions in stream - too noisy
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
else if (actionType === 'delete') {
|
|
367
|
+
actionLine = `${status} Deleted **${fullPath}**${statusColor}`;
|
|
368
|
+
}
|
|
369
|
+
else if (actionType === 'command') {
|
|
370
|
+
const cmd = actionLog.target;
|
|
371
|
+
// Show command output if available
|
|
372
|
+
if (actionLog.details && actionLog.details.trim()) {
|
|
373
|
+
actionLine = `${status} Ran \`${cmd}\`\n\`\`\`\n${actionLog.details.slice(0, 500)}${actionLog.details.length > 500 ? '\n...(truncated)' : ''}\n\`\`\``;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
actionLine = `${status} Ran \`${cmd}\`${statusColor}`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
else if (actionType === 'search') {
|
|
380
|
+
// Don't show search actions - too noisy
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
else if (actionType === 'mkdir') {
|
|
384
|
+
actionLine = `${status} Created directory **${fullPath}**${statusColor}`;
|
|
385
|
+
}
|
|
386
|
+
else if (actionType === 'fetch') {
|
|
387
|
+
actionLine = `${status} Fetched ${target}${statusColor}`;
|
|
388
|
+
}
|
|
389
|
+
else if (actionType === 'list') {
|
|
390
|
+
// Don't show list actions
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
actionLine = `◦ ${actionType}: ${target}`;
|
|
395
|
+
}
|
|
396
|
+
if (actionLine) {
|
|
397
|
+
setAgentStreamingContent(prev => prev + (prev ? '\n\n' : '') + actionLine);
|
|
398
|
+
}
|
|
325
399
|
},
|
|
326
400
|
onThinking: (text) => {
|
|
327
401
|
// Strip <think> and <tool_call> tags from thinking text
|
|
@@ -983,6 +1057,57 @@ export const App = () => {
|
|
|
983
1057
|
}]);
|
|
984
1058
|
break;
|
|
985
1059
|
}
|
|
1060
|
+
case '/scan': {
|
|
1061
|
+
if (!projectContext) {
|
|
1062
|
+
notify('No project context');
|
|
1063
|
+
break;
|
|
1064
|
+
}
|
|
1065
|
+
// Check for subcommands
|
|
1066
|
+
if (args[0] === 'status') {
|
|
1067
|
+
const intel = loadProjectIntelligence(projectContext.root);
|
|
1068
|
+
if (intel) {
|
|
1069
|
+
const age = Math.round((Date.now() - new Date(intel.scannedAt).getTime()) / (1000 * 60 * 60));
|
|
1070
|
+
notify(`Last scan: ${age}h ago | ${intel.structure.totalFiles} files | ${intel.type}`);
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
notify('No scan data. Run /scan to analyze project.');
|
|
1074
|
+
}
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
if (args[0] === 'clear') {
|
|
1078
|
+
// Clear cached intelligence
|
|
1079
|
+
const intelPath = `${projectContext.root}/.codeep/intelligence.json`;
|
|
1080
|
+
try {
|
|
1081
|
+
const fs = require('fs');
|
|
1082
|
+
if (fs.existsSync(intelPath)) {
|
|
1083
|
+
fs.unlinkSync(intelPath);
|
|
1084
|
+
notify('Project intelligence cleared');
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
notify('No cached intelligence to clear');
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
catch {
|
|
1091
|
+
notify('Failed to clear intelligence');
|
|
1092
|
+
}
|
|
1093
|
+
break;
|
|
1094
|
+
}
|
|
1095
|
+
// Run full scan
|
|
1096
|
+
notify('Scanning project...');
|
|
1097
|
+
scanProject(projectContext.root).then(intelligence => {
|
|
1098
|
+
saveProjectIntelligence(projectContext.root, intelligence);
|
|
1099
|
+
// Generate and display summary
|
|
1100
|
+
const context = generateContextFromIntelligence(intelligence);
|
|
1101
|
+
setMessages(prev => [...prev, {
|
|
1102
|
+
role: 'assistant',
|
|
1103
|
+
content: `# Project Scan Complete\n\n${context}\n\n---\n*Saved to .codeep/intelligence.json*`,
|
|
1104
|
+
}]);
|
|
1105
|
+
notify(`Scanned: ${intelligence.structure.totalFiles} files, ${intelligence.structure.totalDirectories} dirs`);
|
|
1106
|
+
}).catch(error => {
|
|
1107
|
+
notify('Scan failed: ' + error.message);
|
|
1108
|
+
});
|
|
1109
|
+
break;
|
|
1110
|
+
}
|
|
986
1111
|
case '/learn': {
|
|
987
1112
|
if (!projectContext) {
|
|
988
1113
|
notify('No project context');
|
|
@@ -1319,7 +1444,7 @@ export const App = () => {
|
|
|
1319
1444
|
// Helper to check if we're showing an inline menu
|
|
1320
1445
|
const isInlineMenu = ['help', 'status', 'settings', 'sessions', 'sessions-delete',
|
|
1321
1446
|
'logout', 'search', 'export', 'model', 'provider', 'protocol', 'language'].includes(screen);
|
|
1322
|
-
return (_jsxs(Box, { flexDirection: "column", children: [messages.length === 0 && !isLoading && _jsx(Logo, {}), messages.length === 0 && !isLoading && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { justifyContent: "center", children: _jsxs(Text, { children: ["Connected to ", _jsx(Text, { color: "#f02a30", children: config.get('model') }), ". Type ", _jsx(Text, { color: "#f02a30", children: "/help" }), " for commands."] }) }), _jsx(Text, { children: " " }), _jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: "cyan", bold: true, children: "Welcome to Codeep - Your AI Coding Assistant" }) }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", paddingX: 2, children: [_jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Ask questions about your code or request implementations"] }), _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Use ", _jsxs(Text, { color: "cyan", children: ["/agent ", '<task>'] }), " for autonomous task execution"] }), _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Type ", _jsx(Text, { color: "cyan", children: "/diff" }), " to review changes, ", _jsx(Text, { color: "cyan", children: "/commit" }), " to generate commit messages"] }), _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Configure settings with ", _jsx(Text, { color: "cyan", children: "/settings" }), " - enable Agent Mode for auto-execution"] })] }), _jsx(Text, { children: " " }), _jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: "gray", children: "Start typing your message or use a command to begin..." }) }), _jsx(Text, { children: " " })] })), _jsx(MessageList, { messages: messages, streamingContent: streamingContent }, sessionId), isLoading && !isAgentRunning && _jsx(Loading, { isStreaming: !!streamingContent }),
|
|
1447
|
+
return (_jsxs(Box, { flexDirection: "column", children: [messages.length === 0 && !isLoading && _jsx(Logo, {}), messages.length === 0 && !isLoading && (_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Box, { justifyContent: "center", children: _jsxs(Text, { children: ["Connected to ", _jsx(Text, { color: "#f02a30", children: config.get('model') }), ". Type ", _jsx(Text, { color: "#f02a30", children: "/help" }), " for commands."] }) }), _jsx(Text, { children: " " }), _jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: "cyan", bold: true, children: "Welcome to Codeep - Your AI Coding Assistant" }) }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", paddingX: 2, children: [_jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Ask questions about your code or request implementations"] }), _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Use ", _jsxs(Text, { color: "cyan", children: ["/agent ", '<task>'] }), " for autonomous task execution"] }), _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Type ", _jsx(Text, { color: "cyan", children: "/diff" }), " to review changes, ", _jsx(Text, { color: "cyan", children: "/commit" }), " to generate commit messages"] }), _jsxs(Text, { children: [_jsx(Text, { color: "#f02a30", children: "\u2022" }), " Configure settings with ", _jsx(Text, { color: "cyan", children: "/settings" }), " - enable Agent Mode for auto-execution"] })] }), _jsx(Text, { children: " " }), _jsx(Box, { justifyContent: "center", children: _jsx(Text, { color: "gray", children: "Start typing your message or use a command to begin..." }) }), _jsx(Text, { children: " " })] })), _jsx(MessageList, { messages: messages, streamingContent: streamingContent, agentStreamingContent: isAgentRunning ? agentStreamingContent : undefined }, sessionId), isLoading && !isAgentRunning && _jsx(Loading, { isStreaming: !!streamingContent }), isAgentRunning && (_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: agentDryRun ? 'yellow' : '#f02a30', children: [agentDryRun ? '[DRY RUN]' : '[AGENT]', " Step ", agentIteration, " | ", agentActions.length, " actions"] }), _jsx(Text, { color: "gray", children: " | Press Esc to stop" })] }, "agent-status")), pendingFileChanges.length > 0 && !isLoading && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, marginY: 1, children: [_jsxs(Text, { color: "#f02a30", bold: true, children: ["\u2713 Detected ", pendingFileChanges.length, " file change(s):"] }), pendingFileChanges.map((change, i) => {
|
|
1323
1448
|
const actionColor = change.action === 'delete' ? 'red' : change.action === 'edit' ? 'yellow' : 'green';
|
|
1324
1449
|
const actionLabel = change.action === 'delete' ? 'DELETE' : change.action === 'edit' ? 'EDIT' : 'CREATE';
|
|
1325
1450
|
return (_jsxs(Text, { children: ["\u2022 ", _jsxs(Text, { color: actionColor, children: ["[", actionLabel, "]"] }), " ", change.path, change.action !== 'delete' && change.content.includes('\n') && ` (${change.content.split('\n').length} lines)`] }, i));
|
|
@@ -14,12 +14,12 @@ interface AgentProgressProps {
|
|
|
14
14
|
}
|
|
15
15
|
export declare const AgentProgress: React.FC<AgentProgressProps>;
|
|
16
16
|
/**
|
|
17
|
-
* Live
|
|
17
|
+
* Live Statistics component - shows real-time agent progress
|
|
18
18
|
*
|
|
19
|
-
* KEY DESIGN:
|
|
20
|
-
* - While running: shows live
|
|
21
|
-
* - When finished: shows summary
|
|
22
|
-
* -
|
|
19
|
+
* KEY DESIGN: Always renders exactly FIXED_HEIGHT lines to prevent terminal jumping
|
|
20
|
+
* - While running: shows current action + live statistics
|
|
21
|
+
* - When finished: shows final summary (same height)
|
|
22
|
+
* - NEVER returns null - always renders the same box structure
|
|
23
23
|
*/
|
|
24
24
|
interface LiveCodeStreamProps {
|
|
25
25
|
actions: ActionLog[];
|
|
@@ -145,21 +145,17 @@ const isSectionBreak = (line, prevLine) => {
|
|
|
145
145
|
}
|
|
146
146
|
return false;
|
|
147
147
|
};
|
|
148
|
-
const
|
|
148
|
+
const FIXED_HEIGHT = 6; // Fixed number of content lines (excluding border)
|
|
149
149
|
export const LiveCodeStream = memo(({ actions, isRunning, terminalWidth = 80 }) => {
|
|
150
|
-
// Find the current write/edit action for live preview (skip read actions)
|
|
151
150
|
const currentAction = actions.length > 0 ? actions[actions.length - 1] : null;
|
|
152
|
-
const isCodeAction = currentAction && (currentAction.type === 'write' || currentAction.type === 'edit');
|
|
153
|
-
// Skip rendering for read actions - no preview needed
|
|
154
|
-
const isReadAction = currentAction && currentAction.type === 'read';
|
|
155
|
-
// ALL HOOKS MUST BE CALLED BEFORE ANY EARLY RETURNS (Rules of Hooks)
|
|
156
151
|
// Calculate statistics from all actions
|
|
157
152
|
const stats = useMemo(() => {
|
|
158
153
|
const filesCreated = actions.filter(a => a.type === 'write' && a.result === 'success');
|
|
159
154
|
const filesEdited = actions.filter(a => a.type === 'edit' && a.result === 'success');
|
|
160
155
|
const filesDeleted = actions.filter(a => a.type === 'delete' && a.result === 'success');
|
|
161
|
-
const filesRead = actions.filter(a => a.type === 'read'
|
|
162
|
-
const commands = actions.filter(a => a.type === 'command'
|
|
156
|
+
const filesRead = actions.filter(a => a.type === 'read');
|
|
157
|
+
const commands = actions.filter(a => a.type === 'command');
|
|
158
|
+
const searches = actions.filter(a => a.type === 'search');
|
|
163
159
|
const errors = actions.filter(a => a.result === 'error');
|
|
164
160
|
// Calculate total lines written
|
|
165
161
|
let totalLinesWritten = 0;
|
|
@@ -174,55 +170,114 @@ export const LiveCodeStream = memo(({ actions, isRunning, terminalWidth = 80 })
|
|
|
174
170
|
filesDeleted: filesDeleted.length,
|
|
175
171
|
filesRead: filesRead.length,
|
|
176
172
|
commands: commands.length,
|
|
173
|
+
searches: searches.length,
|
|
177
174
|
errors: errors.length,
|
|
178
175
|
totalLinesWritten,
|
|
179
|
-
|
|
180
|
-
editedFiles: filesEdited.map(a => a.target.split('/').pop() || a.target),
|
|
176
|
+
totalActions: actions.length,
|
|
181
177
|
};
|
|
182
178
|
}, [actions]);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
179
|
+
const boxWidth = Math.min(terminalWidth - 4, 60);
|
|
180
|
+
// Build content lines array - always exactly FIXED_HEIGHT lines
|
|
181
|
+
const contentLines = [];
|
|
182
|
+
if (isRunning) {
|
|
183
|
+
// Line 1: Current action
|
|
184
|
+
if (currentAction) {
|
|
185
|
+
contentLines.push(_jsxs(Box, { children: [_jsxs(Text, { color: getActionColor(currentAction.type), bold: true, children: [getActionLabel(currentAction.type), " "] }), _jsx(Text, { color: "white", children: formatTarget(currentAction.target, boxWidth - 15) })] }, "current"));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
contentLines.push(_jsx(Text, { color: "gray", children: "Starting..." }, "current"));
|
|
189
|
+
}
|
|
190
|
+
// Line 2: Separator
|
|
191
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: '─'.repeat(boxWidth - 4) }, "sep"));
|
|
192
|
+
// Line 3: File changes stats
|
|
193
|
+
const changesParts = [];
|
|
194
|
+
if (stats.filesCreated > 0)
|
|
195
|
+
changesParts.push(`+${stats.filesCreated} created`);
|
|
196
|
+
if (stats.filesEdited > 0)
|
|
197
|
+
changesParts.push(`~${stats.filesEdited} edited`);
|
|
198
|
+
if (stats.filesDeleted > 0)
|
|
199
|
+
changesParts.push(`-${stats.filesDeleted} deleted`);
|
|
200
|
+
if (changesParts.length > 0) {
|
|
201
|
+
contentLines.push(_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "Files: " }), _jsx(Text, { color: "green", children: changesParts.join(' ') })] }, "changes"));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: "Files: no changes yet" }, "changes"));
|
|
205
|
+
}
|
|
206
|
+
// Line 4: Other stats
|
|
207
|
+
const otherParts = [];
|
|
208
|
+
if (stats.filesRead > 0)
|
|
209
|
+
otherParts.push(`${stats.filesRead}R`);
|
|
210
|
+
if (stats.commands > 0)
|
|
211
|
+
otherParts.push(`${stats.commands}C`);
|
|
212
|
+
if (stats.searches > 0)
|
|
213
|
+
otherParts.push(`${stats.searches}S`);
|
|
214
|
+
if (stats.totalLinesWritten > 0)
|
|
215
|
+
otherParts.push(`${stats.totalLinesWritten}L`);
|
|
216
|
+
contentLines.push(_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "Stats: " }), _jsx(Text, { color: "gray", children: otherParts.length > 0 ? otherParts.join(' | ') : 'gathering...' })] }, "other"));
|
|
217
|
+
// Line 5: Errors if any
|
|
218
|
+
if (stats.errors > 0) {
|
|
219
|
+
contentLines.push(_jsxs(Text, { color: "red", children: [stats.errors, " error(s)"] }, "errors"));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: " " }, "errors"));
|
|
223
|
+
}
|
|
224
|
+
// Line 6: Help text
|
|
225
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: "Press Esc to stop" }, "help"));
|
|
211
226
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
else {
|
|
228
|
+
// FINISHED STATE
|
|
229
|
+
// Line 1: Complete header
|
|
230
|
+
contentLines.push(_jsxs(Text, { color: stats.errors > 0 ? 'red' : 'green', bold: true, children: [stats.errors > 0 ? 'Completed with errors' : 'Completed', " (", stats.totalActions, " actions)"] }, "header"));
|
|
231
|
+
// Line 2: Separator
|
|
232
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: '─'.repeat(boxWidth - 4) }, "sep"));
|
|
233
|
+
// Line 3: File changes
|
|
234
|
+
const changesParts = [];
|
|
235
|
+
if (stats.filesCreated > 0)
|
|
236
|
+
changesParts.push(`+${stats.filesCreated} created`);
|
|
237
|
+
if (stats.filesEdited > 0)
|
|
238
|
+
changesParts.push(`~${stats.filesEdited} edited`);
|
|
239
|
+
if (stats.filesDeleted > 0)
|
|
240
|
+
changesParts.push(`-${stats.filesDeleted} deleted`);
|
|
241
|
+
if (changesParts.length > 0) {
|
|
242
|
+
contentLines.push(_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "Files: " }), _jsx(Text, { color: "green", children: changesParts.join(' ') })] }, "changes"));
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
contentLines.push(_jsx(Text, { color: "gray", children: "Files: no changes made" }, "changes"));
|
|
246
|
+
}
|
|
247
|
+
// Line 4: Lines written
|
|
248
|
+
if (stats.totalLinesWritten > 0) {
|
|
249
|
+
contentLines.push(_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "Lines: " }), _jsxs(Text, { color: "white", children: [stats.totalLinesWritten, " written"] })] }, "lines"));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: " " }, "lines"));
|
|
253
|
+
}
|
|
254
|
+
// Line 5: Other stats
|
|
255
|
+
const otherParts = [];
|
|
256
|
+
if (stats.filesRead > 0)
|
|
257
|
+
otherParts.push(`${stats.filesRead} read`);
|
|
258
|
+
if (stats.commands > 0)
|
|
259
|
+
otherParts.push(`${stats.commands} commands`);
|
|
260
|
+
if (stats.searches > 0)
|
|
261
|
+
otherParts.push(`${stats.searches} searches`);
|
|
262
|
+
if (otherParts.length > 0) {
|
|
263
|
+
contentLines.push(_jsxs(Text, { color: "gray", dimColor: true, children: ["Also: ", otherParts.join(', ')] }, "other"));
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: " " }, "other"));
|
|
267
|
+
}
|
|
268
|
+
// Line 6: Errors
|
|
269
|
+
if (stats.errors > 0) {
|
|
270
|
+
contentLines.push(_jsxs(Text, { color: "red", children: [stats.errors, " error(s) occurred"] }, "errors"));
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: " " }, "errors"));
|
|
274
|
+
}
|
|
217
275
|
}
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
276
|
+
// Pad to fixed height if needed
|
|
277
|
+
while (contentLines.length < FIXED_HEIGHT) {
|
|
278
|
+
contentLines.push(_jsx(Text, { color: "gray", dimColor: true, children: " " }, `pad-${contentLines.length}`));
|
|
221
279
|
}
|
|
222
|
-
|
|
223
|
-
const boxWidth = Math.min(terminalWidth - 4, 76);
|
|
224
|
-
const hasChanges = stats.filesCreated > 0 || stats.filesEdited > 0 || stats.filesDeleted > 0;
|
|
225
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: stats.errors > 0 ? 'red' : 'green', marginBottom: 1, width: boxWidth, children: [_jsxs(Box, { paddingX: 1, children: [_jsxs(Text, { color: stats.errors > 0 ? 'red' : 'green', bold: true, children: [stats.errors > 0 ? '!' : '✓', " Session Complete"] }), _jsxs(Text, { color: "gray", children: [" \u2022 ", actions.length, " actions"] })] }), _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [hasChanges ? (_jsxs(_Fragment, { children: [stats.filesCreated > 0 && (_jsxs(Text, { children: [_jsxs(Text, { color: "green", bold: true, children: [" + ", stats.filesCreated, " "] }), _jsx(Text, { color: "gray", children: "file(s) created" }), stats.createdFiles.length <= 3 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" (", stats.createdFiles.join(', '), ")"] }))] })), stats.filesEdited > 0 && (_jsxs(Text, { children: [_jsxs(Text, { color: "yellow", bold: true, children: [" ~ ", stats.filesEdited, " "] }), _jsx(Text, { color: "gray", children: "file(s) modified" }), stats.editedFiles.length <= 3 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" (", stats.editedFiles.join(', '), ")"] }))] })), stats.filesDeleted > 0 && (_jsxs(Text, { children: [_jsxs(Text, { color: "red", bold: true, children: [" - ", stats.filesDeleted, " "] }), _jsx(Text, { color: "gray", children: "file(s) deleted" })] })), stats.totalLinesWritten > 0 && (_jsxs(Text, { children: [_jsxs(Text, { color: "cyan", bold: true, children: [" \u2261 ", stats.totalLinesWritten, " "] }), _jsx(Text, { color: "gray", children: "total lines written" })] })), stats.filesRead > 0 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", stats.filesRead, " file(s) read"] })), stats.commands > 0 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", stats.commands, " command(s) run"] }))] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: " No file changes made" }), stats.filesRead > 0 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", stats.filesRead, " file(s) read"] })), stats.commands > 0 && (_jsxs(Text, { color: "gray", dimColor: true, children: [" ", stats.commands, " command(s) run"] }))] })), stats.errors > 0 && (_jsxs(Text, { color: "red", children: [" \u2717 ", stats.errors, " error(s) occurred"] })), Array.from({ length: Math.max(0, MAX_PREVIEW_LINES - 6) }).map((_, i) => (_jsx(Text, { color: "gray", dimColor: true, children: " " }, `pad-${i}`)))] })] }));
|
|
280
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: isRunning ? 'cyan' : (stats.errors > 0 ? 'red' : 'green'), paddingX: 1, marginBottom: 1, width: boxWidth, children: contentLines }));
|
|
226
281
|
});
|
|
227
282
|
LiveCodeStream.displayName = 'LiveCodeStream';
|
|
228
283
|
// Helper functions for action display
|
|
@@ -254,17 +309,18 @@ const getActionLabel = (type) => {
|
|
|
254
309
|
default: return type.toUpperCase();
|
|
255
310
|
}
|
|
256
311
|
};
|
|
257
|
-
const formatTarget = (target) => {
|
|
312
|
+
const formatTarget = (target, maxLen = 50) => {
|
|
258
313
|
// For file paths, show just the filename or last part
|
|
259
314
|
if (target.includes('/')) {
|
|
260
315
|
const parts = target.split('/');
|
|
261
316
|
const filename = parts[parts.length - 1];
|
|
262
317
|
if (parts.length > 2) {
|
|
263
|
-
|
|
318
|
+
const short = `.../${parts[parts.length - 2]}/${filename}`;
|
|
319
|
+
return short.length > maxLen ? '...' + short.slice(-(maxLen - 3)) : short;
|
|
264
320
|
}
|
|
265
|
-
return target.length >
|
|
321
|
+
return target.length > maxLen ? '...' + target.slice(-(maxLen - 3)) : target;
|
|
266
322
|
}
|
|
267
|
-
return target.length >
|
|
323
|
+
return target.length > maxLen ? target.slice(0, maxLen - 3) + '...' : target;
|
|
268
324
|
};
|
|
269
325
|
/**
|
|
270
326
|
* Single action item display
|
|
@@ -21,9 +21,9 @@ MemoizedMessage.displayName = 'MemoizedMessage';
|
|
|
21
21
|
* NOTE: This is a temporary solution until we implement a custom renderer
|
|
22
22
|
* like Claude CLI uses (DEC Mode 2026 / synchronized output).
|
|
23
23
|
*/
|
|
24
|
-
export const MessageList = memo(({ messages, streamingContent, }) => {
|
|
24
|
+
export const MessageList = memo(({ messages, streamingContent, agentStreamingContent, }) => {
|
|
25
25
|
// Memoize the messages array rendering
|
|
26
26
|
const renderedMessages = useMemo(() => (messages.map((msg, index) => (_jsx(MemoizedMessage, { msg: msg, index: index }, `msg-${index}-${msg.role}`)))), [messages]);
|
|
27
|
-
return (_jsxs(Box, { flexDirection: "column", children: [renderedMessages, streamingContent && (_jsx(StreamingMessage, { content: streamingContent }))] }));
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "column", children: [renderedMessages, streamingContent && (_jsx(StreamingMessage, { content: streamingContent })), agentStreamingContent && (_jsx(StreamingMessage, { content: `**Agent Actions:**\n${agentStreamingContent}` }))] }));
|
|
28
28
|
});
|
|
29
29
|
MessageList.displayName = 'MessageList';
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useInput } from 'ink';
|
|
4
|
-
import { setProjectPermission } from '../config/index.js';
|
|
4
|
+
import { setProjectPermission, hasStandardProjectMarkers, initializeAsProject } from '../config/index.js';
|
|
5
5
|
import { getProjectSummary } from '../utils/project.js';
|
|
6
6
|
export const ProjectPermission = ({ projectPath, onComplete }) => {
|
|
7
|
-
|
|
7
|
+
// If folder doesn't have standard project markers, ask if user wants to initialize it
|
|
8
|
+
const isStandardProject = hasStandardProjectMarkers(projectPath);
|
|
9
|
+
const [step, setStep] = useState(isStandardProject ? 'read' : 'init');
|
|
8
10
|
const [readGranted, setReadGranted] = useState(false);
|
|
9
11
|
const summary = getProjectSummary(projectPath);
|
|
10
12
|
const projectName = summary?.name || projectPath.split('/').pop() || 'Unknown';
|
|
11
13
|
useInput((input, key) => {
|
|
12
14
|
const char = input.toLowerCase();
|
|
13
|
-
if (step === '
|
|
15
|
+
if (step === 'init') {
|
|
16
|
+
if (char === 'y') {
|
|
17
|
+
// Initialize as project and continue to permissions
|
|
18
|
+
initializeAsProject(projectPath);
|
|
19
|
+
setStep('read');
|
|
20
|
+
}
|
|
21
|
+
else if (char === 'n' || key.escape) {
|
|
22
|
+
// Don't initialize - use global config, skip to read permission
|
|
23
|
+
setStep('read');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (step === 'read') {
|
|
14
27
|
if (char === 'y') {
|
|
15
28
|
// Grant for this session only
|
|
16
29
|
setReadGranted(true);
|
|
@@ -48,5 +61,5 @@ export const ProjectPermission = ({ projectPath, onComplete }) => {
|
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
});
|
|
51
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, children: [_jsx(Text, { color: "#f02a30", bold: true, children: "Project Access Request" }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { children: "Project: " }), _jsx(Text, { color: "cyan", bold: true, children: projectName })] }), _jsxs(Text, { children: [_jsx(Text, { children: "Path: " }), _jsx(Text, { children: projectPath })] }), summary && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { children: "Type: " }), _jsx(Text, { children: summary.type })] }), _jsxs(Text, { children: [_jsx(Text, { children: "Files: " }), _jsxs(Text, { children: [summary.fileCount, " code files"] })] })] }))] }), _jsx(Text, { children: " " }), step === 'read' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "#f02a30", children: "Allow Codeep to read project files?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "This enables:" }), _jsx(Text, { children: " \u2022 Auto-detect file paths in your messages" }), _jsx(Text, { children: " \u2022 Send file contents to AI for analysis" }), _jsx(Text, { children: " \u2022 Project structure awareness" }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes (this session) " }), _jsx(Text, { color: "cyan", children: "[A]" }), _jsx(Text, { children: " Always " }), _jsx(Text, { color: "#f02a30", children: "[N]" }), _jsx(Text, { children: " No" })] })] })), step === 'write' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "green", children: "\u2713 Read permission granted" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "#f02a30", children: "Allow Codeep to suggest file changes?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "This enables:" }), _jsx(Text, { children: " \u2022 AI can suggest code modifications" }), _jsx(Text, { children: " \u2022 You'll always see changes before applying" }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes (this session) " }), _jsx(Text, { color: "cyan", children: "[A]" }), _jsx(Text, { children: " Always " }), _jsx(Text, { color: "#f02a30", children: "[N]" }), _jsx(Text, { children: " Read-only" })] })] }))] }));
|
|
64
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "#f02a30", padding: 1, children: [_jsx(Text, { color: "#f02a30", bold: true, children: "Project Access Request" }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsx(Text, { children: "Project: " }), _jsx(Text, { color: "cyan", bold: true, children: projectName })] }), _jsxs(Text, { children: [_jsx(Text, { children: "Path: " }), _jsx(Text, { children: projectPath })] }), summary && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [_jsx(Text, { children: "Type: " }), _jsx(Text, { children: summary.type })] }), _jsxs(Text, { children: [_jsx(Text, { children: "Files: " }), _jsxs(Text, { children: [summary.fileCount, " code files"] })] })] }))] }), _jsx(Text, { children: " " }), step === 'init' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "yellow", children: "This folder is not recognized as a project." }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Initialize as a Codeep project?" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: "This will create a .codeep/ folder to store:" }), _jsx(Text, { color: "gray", children: " \u2022 Session history" }), _jsx(Text, { color: "gray", children: " \u2022 Project-specific settings" }), _jsx(Text, { color: "gray", children: " \u2022 Permission preferences" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "gray", children: "If you choose No, settings will be stored globally." }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes, initialize " }), _jsx(Text, { color: "cyan", children: "[N]" }), _jsx(Text, { children: " No, use global config" })] })] })), step === 'read' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "#f02a30", children: "Allow Codeep to read project files?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "This enables:" }), _jsx(Text, { children: " \u2022 Auto-detect file paths in your messages" }), _jsx(Text, { children: " \u2022 Send file contents to AI for analysis" }), _jsx(Text, { children: " \u2022 Project structure awareness" }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes (this session) " }), _jsx(Text, { color: "cyan", children: "[A]" }), _jsx(Text, { children: " Always " }), _jsx(Text, { color: "#f02a30", children: "[N]" }), _jsx(Text, { children: " No" })] })] })), step === 'write' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "green", children: "\u2713 Read permission granted" }), _jsx(Text, { children: " " }), _jsx(Text, { color: "#f02a30", children: "Allow Codeep to suggest file changes?" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "This enables:" }), _jsx(Text, { children: " \u2022 AI can suggest code modifications" }), _jsx(Text, { children: " \u2022 You'll always see changes before applying" }), _jsx(Text, { children: " " }), _jsxs(Box, { children: [_jsx(Text, { color: "green", children: "[Y]" }), _jsx(Text, { children: " Yes (this session) " }), _jsx(Text, { color: "cyan", children: "[A]" }), _jsx(Text, { children: " Always " }), _jsx(Text, { color: "#f02a30", children: "[N]" }), _jsx(Text, { children: " Read-only" })] })] }))] }));
|
|
52
65
|
};
|
package/dist/config/index.d.ts
CHANGED
|
@@ -43,6 +43,19 @@ interface ConfigSchema {
|
|
|
43
43
|
}
|
|
44
44
|
export type { AgentMode };
|
|
45
45
|
export type { LanguageCode };
|
|
46
|
+
/**
|
|
47
|
+
* Check if directory has standard project markers (not manually initialized)
|
|
48
|
+
*/
|
|
49
|
+
export declare function hasStandardProjectMarkers(path: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Initialize a folder as a Codeep project
|
|
52
|
+
* Creates .codeep/project.json marker file
|
|
53
|
+
*/
|
|
54
|
+
export declare function initializeAsProject(path: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Check if folder was manually initialized as project
|
|
57
|
+
*/
|
|
58
|
+
export declare function isManuallyInitializedProject(path: string): boolean;
|
|
46
59
|
export declare const config: Conf<ConfigSchema>;
|
|
47
60
|
export declare const LANGUAGES: Record<string, string>;
|
|
48
61
|
export declare const PROTOCOLS: Record<string, string>;
|
package/dist/config/index.js
CHANGED
|
@@ -32,8 +32,14 @@ function getLocalConfigPath(projectPath) {
|
|
|
32
32
|
/**
|
|
33
33
|
* Check if directory is a project
|
|
34
34
|
* Looks for common project indicators: package.json, pyproject.toml, Cargo.toml, go.mod, composer.json, etc.
|
|
35
|
+
* Also checks if user has manually initialized this folder as a project (.codeep/project.json)
|
|
35
36
|
*/
|
|
36
37
|
function isProjectDirectory(path) {
|
|
38
|
+
// Check if user has manually initialized this as a project
|
|
39
|
+
const manualProjectMarker = join(path, '.codeep', 'project.json');
|
|
40
|
+
if (existsSync(manualProjectMarker)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
37
43
|
const projectFiles = [
|
|
38
44
|
'package.json', // Node.js
|
|
39
45
|
'pyproject.toml', // Python (Poetry)
|
|
@@ -48,6 +54,53 @@ function isProjectDirectory(path) {
|
|
|
48
54
|
];
|
|
49
55
|
return projectFiles.some(file => existsSync(join(path, file)));
|
|
50
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if directory has standard project markers (not manually initialized)
|
|
59
|
+
*/
|
|
60
|
+
export function hasStandardProjectMarkers(path) {
|
|
61
|
+
const projectFiles = [
|
|
62
|
+
'package.json',
|
|
63
|
+
'pyproject.toml',
|
|
64
|
+
'requirements.txt',
|
|
65
|
+
'setup.py',
|
|
66
|
+
'Cargo.toml',
|
|
67
|
+
'go.mod',
|
|
68
|
+
'composer.json',
|
|
69
|
+
'pom.xml',
|
|
70
|
+
'build.gradle',
|
|
71
|
+
'.git',
|
|
72
|
+
];
|
|
73
|
+
return projectFiles.some(file => existsSync(join(path, file)));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Initialize a folder as a Codeep project
|
|
77
|
+
* Creates .codeep/project.json marker file
|
|
78
|
+
*/
|
|
79
|
+
export function initializeAsProject(path) {
|
|
80
|
+
try {
|
|
81
|
+
const codeepDir = join(path, '.codeep');
|
|
82
|
+
if (!existsSync(codeepDir)) {
|
|
83
|
+
mkdirSync(codeepDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
const projectFile = join(codeepDir, 'project.json');
|
|
86
|
+
const projectData = {
|
|
87
|
+
name: path.split('/').pop() || 'project',
|
|
88
|
+
initializedAt: new Date().toISOString(),
|
|
89
|
+
version: '1.0',
|
|
90
|
+
};
|
|
91
|
+
writeFileSync(projectFile, JSON.stringify(projectData, null, 2));
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Check if folder was manually initialized as project
|
|
100
|
+
*/
|
|
101
|
+
export function isManuallyInitializedProject(path) {
|
|
102
|
+
return existsSync(join(path, '.codeep', 'project.json'));
|
|
103
|
+
}
|
|
51
104
|
/**
|
|
52
105
|
* Get fallback config directory if standard location is not writable
|
|
53
106
|
* Falls back to .codeep in current working directory
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Intelligence - Deep project analysis and caching
|
|
3
|
+
* Scans project once and caches important information for faster AI context
|
|
4
|
+
*/
|
|
5
|
+
export interface ProjectIntelligence {
|
|
6
|
+
version: string;
|
|
7
|
+
scannedAt: string;
|
|
8
|
+
projectPath: string;
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
structure: {
|
|
13
|
+
totalFiles: number;
|
|
14
|
+
totalDirectories: number;
|
|
15
|
+
languages: Record<string, number>;
|
|
16
|
+
topDirectories: string[];
|
|
17
|
+
};
|
|
18
|
+
dependencies: {
|
|
19
|
+
runtime: string[];
|
|
20
|
+
dev: string[];
|
|
21
|
+
frameworks: string[];
|
|
22
|
+
};
|
|
23
|
+
keyFiles: {
|
|
24
|
+
path: string;
|
|
25
|
+
summary: string;
|
|
26
|
+
}[];
|
|
27
|
+
entryPoints: string[];
|
|
28
|
+
scripts: Record<string, string>;
|
|
29
|
+
architecture: {
|
|
30
|
+
patterns: string[];
|
|
31
|
+
mainModules: string[];
|
|
32
|
+
apiEndpoints?: string[];
|
|
33
|
+
components?: string[];
|
|
34
|
+
};
|
|
35
|
+
conventions: {
|
|
36
|
+
indentation: 'tabs' | 'spaces' | 'mixed';
|
|
37
|
+
quotes: 'single' | 'double' | 'mixed';
|
|
38
|
+
semicolons: boolean;
|
|
39
|
+
namingStyle: 'camelCase' | 'snake_case' | 'PascalCase' | 'mixed';
|
|
40
|
+
};
|
|
41
|
+
testing: {
|
|
42
|
+
framework: string | null;
|
|
43
|
+
testDirectory: string | null;
|
|
44
|
+
hasTests: boolean;
|
|
45
|
+
};
|
|
46
|
+
notes: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Scan project and generate intelligence
|
|
50
|
+
*/
|
|
51
|
+
export declare function scanProject(projectPath: string): Promise<ProjectIntelligence>;
|
|
52
|
+
/**
|
|
53
|
+
* Save intelligence to .codeep/intelligence.json
|
|
54
|
+
*/
|
|
55
|
+
export declare function saveProjectIntelligence(projectPath: string, intelligence: ProjectIntelligence): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Load intelligence from .codeep/intelligence.json
|
|
58
|
+
*/
|
|
59
|
+
export declare function loadProjectIntelligence(projectPath: string): ProjectIntelligence | null;
|
|
60
|
+
/**
|
|
61
|
+
* Check if intelligence exists and is fresh (less than 24 hours old)
|
|
62
|
+
*/
|
|
63
|
+
export declare function isIntelligenceFresh(projectPath: string, maxAgeHours?: number): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Generate AI-friendly context from intelligence
|
|
66
|
+
*/
|
|
67
|
+
export declare function generateContextFromIntelligence(intelligence: ProjectIntelligence): string;
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Intelligence - Deep project analysis and caching
|
|
3
|
+
* Scans project once and caches important information for faster AI context
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from 'fs';
|
|
6
|
+
import { join, basename, extname } from 'path';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Constants
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const INTELLIGENCE_VERSION = '1.0';
|
|
11
|
+
const INTELLIGENCE_FILE = 'intelligence.json';
|
|
12
|
+
const IGNORE_DIRS = new Set([
|
|
13
|
+
'node_modules', '.git', 'dist', 'build', '.next', 'coverage',
|
|
14
|
+
'.cache', '.vscode', '.idea', '__pycache__', 'venv', '.env',
|
|
15
|
+
'vendor', 'target', 'out', 'bin', 'obj', '.nuxt', '.output',
|
|
16
|
+
]);
|
|
17
|
+
const LANGUAGE_MAP = {
|
|
18
|
+
'.ts': 'TypeScript', '.tsx': 'TypeScript React',
|
|
19
|
+
'.js': 'JavaScript', '.jsx': 'JavaScript React',
|
|
20
|
+
'.py': 'Python', '.rb': 'Ruby', '.go': 'Go', '.rs': 'Rust',
|
|
21
|
+
'.java': 'Java', '.kt': 'Kotlin', '.swift': 'Swift',
|
|
22
|
+
'.php': 'PHP', '.cs': 'C#', '.cpp': 'C++', '.c': 'C',
|
|
23
|
+
'.vue': 'Vue', '.svelte': 'Svelte',
|
|
24
|
+
'.css': 'CSS', '.scss': 'SCSS', '.less': 'Less',
|
|
25
|
+
'.html': 'HTML', '.json': 'JSON', '.yaml': 'YAML', '.yml': 'YAML',
|
|
26
|
+
'.md': 'Markdown', '.sql': 'SQL', '.sh': 'Shell',
|
|
27
|
+
};
|
|
28
|
+
const FRAMEWORK_INDICATORS = {
|
|
29
|
+
'React': ['react', 'react-dom'],
|
|
30
|
+
'Next.js': ['next'],
|
|
31
|
+
'Vue': ['vue'],
|
|
32
|
+
'Nuxt': ['nuxt'],
|
|
33
|
+
'Angular': ['@angular/core'],
|
|
34
|
+
'Svelte': ['svelte'],
|
|
35
|
+
'Express': ['express'],
|
|
36
|
+
'Fastify': ['fastify'],
|
|
37
|
+
'NestJS': ['@nestjs/core'],
|
|
38
|
+
'Django': ['django'],
|
|
39
|
+
'Flask': ['flask'],
|
|
40
|
+
'FastAPI': ['fastapi'],
|
|
41
|
+
'Rails': ['rails'],
|
|
42
|
+
'Laravel': ['laravel'],
|
|
43
|
+
'Spring': ['spring-boot'],
|
|
44
|
+
};
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Main Functions
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Scan project and generate intelligence
|
|
50
|
+
*/
|
|
51
|
+
export async function scanProject(projectPath) {
|
|
52
|
+
const intelligence = {
|
|
53
|
+
version: INTELLIGENCE_VERSION,
|
|
54
|
+
scannedAt: new Date().toISOString(),
|
|
55
|
+
projectPath,
|
|
56
|
+
name: basename(projectPath),
|
|
57
|
+
type: 'Unknown',
|
|
58
|
+
description: '',
|
|
59
|
+
structure: {
|
|
60
|
+
totalFiles: 0,
|
|
61
|
+
totalDirectories: 0,
|
|
62
|
+
languages: {},
|
|
63
|
+
topDirectories: [],
|
|
64
|
+
},
|
|
65
|
+
dependencies: {
|
|
66
|
+
runtime: [],
|
|
67
|
+
dev: [],
|
|
68
|
+
frameworks: [],
|
|
69
|
+
},
|
|
70
|
+
keyFiles: [],
|
|
71
|
+
entryPoints: [],
|
|
72
|
+
scripts: {},
|
|
73
|
+
architecture: {
|
|
74
|
+
patterns: [],
|
|
75
|
+
mainModules: [],
|
|
76
|
+
},
|
|
77
|
+
conventions: {
|
|
78
|
+
indentation: 'spaces',
|
|
79
|
+
quotes: 'single',
|
|
80
|
+
semicolons: true,
|
|
81
|
+
namingStyle: 'camelCase',
|
|
82
|
+
},
|
|
83
|
+
testing: {
|
|
84
|
+
framework: null,
|
|
85
|
+
testDirectory: null,
|
|
86
|
+
hasTests: false,
|
|
87
|
+
},
|
|
88
|
+
notes: [],
|
|
89
|
+
};
|
|
90
|
+
// Scan directory structure
|
|
91
|
+
scanDirectoryStructure(projectPath, intelligence);
|
|
92
|
+
// Detect project type and dependencies
|
|
93
|
+
detectProjectType(projectPath, intelligence);
|
|
94
|
+
// Analyze key files
|
|
95
|
+
analyzeKeyFiles(projectPath, intelligence);
|
|
96
|
+
// Detect architecture patterns
|
|
97
|
+
detectArchitecture(projectPath, intelligence);
|
|
98
|
+
// Analyze code conventions
|
|
99
|
+
analyzeConventions(projectPath, intelligence);
|
|
100
|
+
// Detect testing setup
|
|
101
|
+
detectTesting(projectPath, intelligence);
|
|
102
|
+
return intelligence;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Save intelligence to .codeep/intelligence.json
|
|
106
|
+
*/
|
|
107
|
+
export function saveProjectIntelligence(projectPath, intelligence) {
|
|
108
|
+
try {
|
|
109
|
+
const codeepDir = join(projectPath, '.codeep');
|
|
110
|
+
if (!existsSync(codeepDir)) {
|
|
111
|
+
mkdirSync(codeepDir, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
const filePath = join(codeepDir, INTELLIGENCE_FILE);
|
|
114
|
+
writeFileSync(filePath, JSON.stringify(intelligence, null, 2));
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Load intelligence from .codeep/intelligence.json
|
|
123
|
+
*/
|
|
124
|
+
export function loadProjectIntelligence(projectPath) {
|
|
125
|
+
try {
|
|
126
|
+
const filePath = join(projectPath, '.codeep', INTELLIGENCE_FILE);
|
|
127
|
+
if (!existsSync(filePath))
|
|
128
|
+
return null;
|
|
129
|
+
const data = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if intelligence exists and is fresh (less than 24 hours old)
|
|
138
|
+
*/
|
|
139
|
+
export function isIntelligenceFresh(projectPath, maxAgeHours = 24) {
|
|
140
|
+
const intelligence = loadProjectIntelligence(projectPath);
|
|
141
|
+
if (!intelligence)
|
|
142
|
+
return false;
|
|
143
|
+
const scannedAt = new Date(intelligence.scannedAt).getTime();
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
const ageHours = (now - scannedAt) / (1000 * 60 * 60);
|
|
146
|
+
return ageHours < maxAgeHours;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate AI-friendly context from intelligence
|
|
150
|
+
*/
|
|
151
|
+
export function generateContextFromIntelligence(intelligence) {
|
|
152
|
+
const lines = [];
|
|
153
|
+
lines.push(`# Project: ${intelligence.name}`);
|
|
154
|
+
lines.push(`Type: ${intelligence.type}`);
|
|
155
|
+
if (intelligence.description) {
|
|
156
|
+
lines.push(`Description: ${intelligence.description}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push('');
|
|
159
|
+
// Structure
|
|
160
|
+
lines.push('## Structure');
|
|
161
|
+
lines.push(`- ${intelligence.structure.totalFiles} files, ${intelligence.structure.totalDirectories} directories`);
|
|
162
|
+
const topLangs = Object.entries(intelligence.structure.languages)
|
|
163
|
+
.sort((a, b) => b[1] - a[1])
|
|
164
|
+
.slice(0, 5)
|
|
165
|
+
.map(([ext, count]) => `${LANGUAGE_MAP[ext] || ext} (${count})`)
|
|
166
|
+
.join(', ');
|
|
167
|
+
if (topLangs) {
|
|
168
|
+
lines.push(`- Languages: ${topLangs}`);
|
|
169
|
+
}
|
|
170
|
+
if (intelligence.structure.topDirectories.length > 0) {
|
|
171
|
+
lines.push(`- Main directories: ${intelligence.structure.topDirectories.join(', ')}`);
|
|
172
|
+
}
|
|
173
|
+
lines.push('');
|
|
174
|
+
// Frameworks
|
|
175
|
+
if (intelligence.dependencies.frameworks.length > 0) {
|
|
176
|
+
lines.push('## Frameworks');
|
|
177
|
+
lines.push(intelligence.dependencies.frameworks.join(', '));
|
|
178
|
+
lines.push('');
|
|
179
|
+
}
|
|
180
|
+
// Architecture
|
|
181
|
+
if (intelligence.architecture.patterns.length > 0 || intelligence.architecture.mainModules.length > 0) {
|
|
182
|
+
lines.push('## Architecture');
|
|
183
|
+
if (intelligence.architecture.patterns.length > 0) {
|
|
184
|
+
lines.push(`Patterns: ${intelligence.architecture.patterns.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
if (intelligence.architecture.mainModules.length > 0) {
|
|
187
|
+
lines.push(`Main modules: ${intelligence.architecture.mainModules.join(', ')}`);
|
|
188
|
+
}
|
|
189
|
+
lines.push('');
|
|
190
|
+
}
|
|
191
|
+
// Entry points
|
|
192
|
+
if (intelligence.entryPoints.length > 0) {
|
|
193
|
+
lines.push('## Entry Points');
|
|
194
|
+
intelligence.entryPoints.forEach(ep => lines.push(`- ${ep}`));
|
|
195
|
+
lines.push('');
|
|
196
|
+
}
|
|
197
|
+
// Scripts
|
|
198
|
+
if (Object.keys(intelligence.scripts).length > 0) {
|
|
199
|
+
lines.push('## Available Scripts');
|
|
200
|
+
Object.entries(intelligence.scripts).slice(0, 10).forEach(([name, cmd]) => {
|
|
201
|
+
lines.push(`- ${name}: ${cmd}`);
|
|
202
|
+
});
|
|
203
|
+
lines.push('');
|
|
204
|
+
}
|
|
205
|
+
// Key files
|
|
206
|
+
if (intelligence.keyFiles.length > 0) {
|
|
207
|
+
lines.push('## Key Files');
|
|
208
|
+
intelligence.keyFiles.forEach(kf => {
|
|
209
|
+
lines.push(`- ${kf.path}: ${kf.summary}`);
|
|
210
|
+
});
|
|
211
|
+
lines.push('');
|
|
212
|
+
}
|
|
213
|
+
// Testing
|
|
214
|
+
if (intelligence.testing.hasTests) {
|
|
215
|
+
lines.push('## Testing');
|
|
216
|
+
lines.push(`Framework: ${intelligence.testing.framework || 'Unknown'}`);
|
|
217
|
+
if (intelligence.testing.testDirectory) {
|
|
218
|
+
lines.push(`Test directory: ${intelligence.testing.testDirectory}`);
|
|
219
|
+
}
|
|
220
|
+
lines.push('');
|
|
221
|
+
}
|
|
222
|
+
// Conventions
|
|
223
|
+
lines.push('## Code Conventions');
|
|
224
|
+
lines.push(`- Indentation: ${intelligence.conventions.indentation}`);
|
|
225
|
+
lines.push(`- Quotes: ${intelligence.conventions.quotes}`);
|
|
226
|
+
lines.push(`- Semicolons: ${intelligence.conventions.semicolons ? 'yes' : 'no'}`);
|
|
227
|
+
lines.push(`- Naming: ${intelligence.conventions.namingStyle}`);
|
|
228
|
+
// Notes
|
|
229
|
+
if (intelligence.notes.length > 0) {
|
|
230
|
+
lines.push('');
|
|
231
|
+
lines.push('## Notes');
|
|
232
|
+
intelligence.notes.forEach(note => lines.push(`- ${note}`));
|
|
233
|
+
}
|
|
234
|
+
return lines.join('\n');
|
|
235
|
+
}
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// Helper Functions
|
|
238
|
+
// ============================================================================
|
|
239
|
+
function scanDirectoryStructure(dir, intelligence, depth = 0) {
|
|
240
|
+
if (depth > 5)
|
|
241
|
+
return; // Max depth
|
|
242
|
+
try {
|
|
243
|
+
const entries = readdirSync(dir);
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
if (IGNORE_DIRS.has(entry))
|
|
246
|
+
continue;
|
|
247
|
+
if (entry.startsWith('.') && entry !== '.env.example')
|
|
248
|
+
continue;
|
|
249
|
+
const fullPath = join(dir, entry);
|
|
250
|
+
try {
|
|
251
|
+
const stat = statSync(fullPath);
|
|
252
|
+
if (stat.isDirectory()) {
|
|
253
|
+
intelligence.structure.totalDirectories++;
|
|
254
|
+
// Track top-level directories
|
|
255
|
+
if (depth === 0) {
|
|
256
|
+
intelligence.structure.topDirectories.push(entry);
|
|
257
|
+
}
|
|
258
|
+
scanDirectoryStructure(fullPath, intelligence, depth + 1);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
intelligence.structure.totalFiles++;
|
|
262
|
+
const ext = extname(entry).toLowerCase();
|
|
263
|
+
if (ext) {
|
|
264
|
+
intelligence.structure.languages[ext] = (intelligence.structure.languages[ext] || 0) + 1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
// Skip inaccessible files
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Skip inaccessible directories
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function detectProjectType(projectPath, intelligence) {
|
|
278
|
+
// Node.js / JavaScript
|
|
279
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
280
|
+
if (existsSync(packageJsonPath)) {
|
|
281
|
+
try {
|
|
282
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
283
|
+
intelligence.name = pkg.name || intelligence.name;
|
|
284
|
+
intelligence.description = pkg.description || '';
|
|
285
|
+
// Detect type
|
|
286
|
+
if (existsSync(join(projectPath, 'tsconfig.json'))) {
|
|
287
|
+
intelligence.type = 'TypeScript/Node.js';
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
intelligence.type = 'JavaScript/Node.js';
|
|
291
|
+
}
|
|
292
|
+
// Dependencies
|
|
293
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
294
|
+
intelligence.dependencies.runtime = Object.keys(pkg.dependencies || {});
|
|
295
|
+
intelligence.dependencies.dev = Object.keys(pkg.devDependencies || {});
|
|
296
|
+
// Detect frameworks
|
|
297
|
+
for (const [framework, indicators] of Object.entries(FRAMEWORK_INDICATORS)) {
|
|
298
|
+
if (indicators.some(ind => allDeps[ind])) {
|
|
299
|
+
intelligence.dependencies.frameworks.push(framework);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Scripts
|
|
303
|
+
intelligence.scripts = pkg.scripts || {};
|
|
304
|
+
// Entry points
|
|
305
|
+
if (pkg.main)
|
|
306
|
+
intelligence.entryPoints.push(pkg.main);
|
|
307
|
+
if (pkg.module)
|
|
308
|
+
intelligence.entryPoints.push(pkg.module);
|
|
309
|
+
if (pkg.bin) {
|
|
310
|
+
if (typeof pkg.bin === 'string') {
|
|
311
|
+
intelligence.entryPoints.push(pkg.bin);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
intelligence.entryPoints.push(...Object.values(pkg.bin));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Invalid package.json
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Python
|
|
324
|
+
if (existsSync(join(projectPath, 'requirements.txt')) ||
|
|
325
|
+
existsSync(join(projectPath, 'pyproject.toml')) ||
|
|
326
|
+
existsSync(join(projectPath, 'setup.py'))) {
|
|
327
|
+
intelligence.type = 'Python';
|
|
328
|
+
// Try to read requirements
|
|
329
|
+
const reqPath = join(projectPath, 'requirements.txt');
|
|
330
|
+
if (existsSync(reqPath)) {
|
|
331
|
+
const content = readFileSync(reqPath, 'utf-8');
|
|
332
|
+
intelligence.dependencies.runtime = content
|
|
333
|
+
.split('\n')
|
|
334
|
+
.map(l => l.trim().split('==')[0].split('>=')[0])
|
|
335
|
+
.filter(l => l && !l.startsWith('#'));
|
|
336
|
+
// Detect frameworks
|
|
337
|
+
if (intelligence.dependencies.runtime.includes('django')) {
|
|
338
|
+
intelligence.dependencies.frameworks.push('Django');
|
|
339
|
+
}
|
|
340
|
+
if (intelligence.dependencies.runtime.includes('flask')) {
|
|
341
|
+
intelligence.dependencies.frameworks.push('Flask');
|
|
342
|
+
}
|
|
343
|
+
if (intelligence.dependencies.runtime.includes('fastapi')) {
|
|
344
|
+
intelligence.dependencies.frameworks.push('FastAPI');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Go
|
|
350
|
+
if (existsSync(join(projectPath, 'go.mod'))) {
|
|
351
|
+
intelligence.type = 'Go';
|
|
352
|
+
try {
|
|
353
|
+
const content = readFileSync(join(projectPath, 'go.mod'), 'utf-8');
|
|
354
|
+
const moduleMatch = content.match(/module\s+(\S+)/);
|
|
355
|
+
if (moduleMatch) {
|
|
356
|
+
intelligence.name = moduleMatch[1].split('/').pop() || intelligence.name;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch { }
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// Rust
|
|
363
|
+
if (existsSync(join(projectPath, 'Cargo.toml'))) {
|
|
364
|
+
intelligence.type = 'Rust';
|
|
365
|
+
try {
|
|
366
|
+
const content = readFileSync(join(projectPath, 'Cargo.toml'), 'utf-8');
|
|
367
|
+
const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
|
|
368
|
+
if (nameMatch) {
|
|
369
|
+
intelligence.name = nameMatch[1];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch { }
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
// PHP
|
|
376
|
+
if (existsSync(join(projectPath, 'composer.json'))) {
|
|
377
|
+
intelligence.type = 'PHP';
|
|
378
|
+
try {
|
|
379
|
+
const composer = JSON.parse(readFileSync(join(projectPath, 'composer.json'), 'utf-8'));
|
|
380
|
+
intelligence.name = composer.name?.split('/').pop() || intelligence.name;
|
|
381
|
+
intelligence.description = composer.description || '';
|
|
382
|
+
if (composer.require?.['laravel/framework']) {
|
|
383
|
+
intelligence.dependencies.frameworks.push('Laravel');
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch { }
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function analyzeKeyFiles(projectPath, intelligence) {
|
|
391
|
+
const keyFilesToAnalyze = [
|
|
392
|
+
{ path: 'README.md', summarize: summarizeReadme },
|
|
393
|
+
{ path: 'readme.md', summarize: summarizeReadme },
|
|
394
|
+
{ path: 'package.json', summarize: summarizePackageJson },
|
|
395
|
+
{ path: 'tsconfig.json', summarize: summarizeTsConfig },
|
|
396
|
+
{ path: 'Dockerfile', summarize: summarizeDockerfile },
|
|
397
|
+
{ path: '.env.example', summarize: summarizeEnvExample },
|
|
398
|
+
];
|
|
399
|
+
for (const { path, summarize } of keyFilesToAnalyze) {
|
|
400
|
+
const fullPath = join(projectPath, path);
|
|
401
|
+
if (existsSync(fullPath)) {
|
|
402
|
+
try {
|
|
403
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
404
|
+
const summary = summarize(content);
|
|
405
|
+
if (summary) {
|
|
406
|
+
intelligence.keyFiles.push({ path, summary });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch { }
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function summarizeReadme(content) {
|
|
414
|
+
// Extract first meaningful paragraph
|
|
415
|
+
const lines = content.split('\n');
|
|
416
|
+
let inHeader = false;
|
|
417
|
+
for (const line of lines) {
|
|
418
|
+
if (line.startsWith('#')) {
|
|
419
|
+
inHeader = true;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (inHeader && line.trim() && !line.startsWith('#') && !line.startsWith('-') && !line.startsWith('*')) {
|
|
423
|
+
return line.trim().slice(0, 150) + (line.length > 150 ? '...' : '');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return 'Project documentation';
|
|
427
|
+
}
|
|
428
|
+
function summarizePackageJson(content) {
|
|
429
|
+
try {
|
|
430
|
+
const pkg = JSON.parse(content);
|
|
431
|
+
const parts = [];
|
|
432
|
+
if (pkg.description)
|
|
433
|
+
parts.push(pkg.description);
|
|
434
|
+
if (pkg.version)
|
|
435
|
+
parts.push(`v${pkg.version}`);
|
|
436
|
+
return parts.join(' - ') || 'Node.js project configuration';
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return 'Node.js project configuration';
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function summarizeTsConfig(content) {
|
|
443
|
+
try {
|
|
444
|
+
const config = JSON.parse(content);
|
|
445
|
+
const target = config.compilerOptions?.target || 'unknown';
|
|
446
|
+
const module = config.compilerOptions?.module || 'unknown';
|
|
447
|
+
return `TypeScript config (target: ${target}, module: ${module})`;
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return 'TypeScript configuration';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function summarizeDockerfile(_content) {
|
|
454
|
+
return 'Container configuration';
|
|
455
|
+
}
|
|
456
|
+
function summarizeEnvExample(content) {
|
|
457
|
+
const vars = content.split('\n').filter(l => l.includes('=') && !l.startsWith('#')).length;
|
|
458
|
+
return `${vars} environment variables`;
|
|
459
|
+
}
|
|
460
|
+
function detectArchitecture(projectPath, intelligence) {
|
|
461
|
+
const topDirs = intelligence.structure.topDirectories;
|
|
462
|
+
// Detect patterns based on directory structure
|
|
463
|
+
if (topDirs.includes('src')) {
|
|
464
|
+
intelligence.architecture.mainModules.push('src');
|
|
465
|
+
}
|
|
466
|
+
// MVC pattern
|
|
467
|
+
if (topDirs.includes('models') || topDirs.includes('views') || topDirs.includes('controllers')) {
|
|
468
|
+
intelligence.architecture.patterns.push('MVC');
|
|
469
|
+
}
|
|
470
|
+
// Component-based (React/Vue/etc)
|
|
471
|
+
if (topDirs.includes('components') || existsSync(join(projectPath, 'src', 'components'))) {
|
|
472
|
+
intelligence.architecture.patterns.push('Component-based');
|
|
473
|
+
intelligence.architecture.mainModules.push('components');
|
|
474
|
+
}
|
|
475
|
+
// API/Services pattern
|
|
476
|
+
if (topDirs.includes('api') || topDirs.includes('services') ||
|
|
477
|
+
existsSync(join(projectPath, 'src', 'api')) || existsSync(join(projectPath, 'src', 'services'))) {
|
|
478
|
+
intelligence.architecture.patterns.push('Service-oriented');
|
|
479
|
+
}
|
|
480
|
+
// Hooks (React)
|
|
481
|
+
if (topDirs.includes('hooks') || existsSync(join(projectPath, 'src', 'hooks'))) {
|
|
482
|
+
intelligence.architecture.mainModules.push('hooks');
|
|
483
|
+
}
|
|
484
|
+
// Utils/Helpers
|
|
485
|
+
if (topDirs.includes('utils') || topDirs.includes('helpers') || topDirs.includes('lib')) {
|
|
486
|
+
intelligence.architecture.mainModules.push('utils');
|
|
487
|
+
}
|
|
488
|
+
// Pages (Next.js, Nuxt, etc)
|
|
489
|
+
if (topDirs.includes('pages') || topDirs.includes('app')) {
|
|
490
|
+
intelligence.architecture.patterns.push('File-based routing');
|
|
491
|
+
}
|
|
492
|
+
// Detect API endpoints
|
|
493
|
+
const apiDir = join(projectPath, 'src', 'api');
|
|
494
|
+
const pagesApiDir = join(projectPath, 'pages', 'api');
|
|
495
|
+
const appApiDir = join(projectPath, 'app', 'api');
|
|
496
|
+
if (existsSync(apiDir) || existsSync(pagesApiDir) || existsSync(appApiDir)) {
|
|
497
|
+
intelligence.architecture.apiEndpoints = [];
|
|
498
|
+
// Could scan for route files here
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function analyzeConventions(projectPath, intelligence) {
|
|
502
|
+
// Find a sample code file to analyze
|
|
503
|
+
const sampleExtensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
504
|
+
let sampleContent = null;
|
|
505
|
+
const srcDir = join(projectPath, 'src');
|
|
506
|
+
const searchDir = existsSync(srcDir) ? srcDir : projectPath;
|
|
507
|
+
try {
|
|
508
|
+
const files = readdirSync(searchDir);
|
|
509
|
+
for (const file of files) {
|
|
510
|
+
const ext = extname(file).toLowerCase();
|
|
511
|
+
if (sampleExtensions.includes(ext)) {
|
|
512
|
+
const content = readFileSync(join(searchDir, file), 'utf-8');
|
|
513
|
+
if (content.length > 100) {
|
|
514
|
+
sampleContent = content;
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
catch { }
|
|
521
|
+
if (!sampleContent)
|
|
522
|
+
return;
|
|
523
|
+
// Analyze indentation
|
|
524
|
+
const tabCount = (sampleContent.match(/^\t/gm) || []).length;
|
|
525
|
+
const spaceCount = (sampleContent.match(/^ /gm) || []).length;
|
|
526
|
+
if (tabCount > spaceCount * 2) {
|
|
527
|
+
intelligence.conventions.indentation = 'tabs';
|
|
528
|
+
}
|
|
529
|
+
else if (spaceCount > tabCount * 2) {
|
|
530
|
+
intelligence.conventions.indentation = 'spaces';
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
intelligence.conventions.indentation = 'mixed';
|
|
534
|
+
}
|
|
535
|
+
// Analyze quotes
|
|
536
|
+
const singleQuotes = (sampleContent.match(/'/g) || []).length;
|
|
537
|
+
const doubleQuotes = (sampleContent.match(/"/g) || []).length;
|
|
538
|
+
if (singleQuotes > doubleQuotes * 1.5) {
|
|
539
|
+
intelligence.conventions.quotes = 'single';
|
|
540
|
+
}
|
|
541
|
+
else if (doubleQuotes > singleQuotes * 1.5) {
|
|
542
|
+
intelligence.conventions.quotes = 'double';
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
intelligence.conventions.quotes = 'mixed';
|
|
546
|
+
}
|
|
547
|
+
// Analyze semicolons
|
|
548
|
+
const semicolons = (sampleContent.match(/;\s*$/gm) || []).length;
|
|
549
|
+
const statements = (sampleContent.match(/\n/g) || []).length;
|
|
550
|
+
intelligence.conventions.semicolons = semicolons > statements * 0.3;
|
|
551
|
+
// Analyze naming
|
|
552
|
+
const camelCase = (sampleContent.match(/[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*/g) || []).length;
|
|
553
|
+
const snakeCase = (sampleContent.match(/[a-z]+_[a-z]+/g) || []).length;
|
|
554
|
+
if (camelCase > snakeCase * 2) {
|
|
555
|
+
intelligence.conventions.namingStyle = 'camelCase';
|
|
556
|
+
}
|
|
557
|
+
else if (snakeCase > camelCase * 2) {
|
|
558
|
+
intelligence.conventions.namingStyle = 'snake_case';
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
intelligence.conventions.namingStyle = 'mixed';
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function detectTesting(projectPath, intelligence) {
|
|
565
|
+
// Check for test directories
|
|
566
|
+
const testDirs = ['test', 'tests', '__tests__', 'spec', 'specs'];
|
|
567
|
+
for (const dir of testDirs) {
|
|
568
|
+
if (existsSync(join(projectPath, dir))) {
|
|
569
|
+
intelligence.testing.hasTests = true;
|
|
570
|
+
intelligence.testing.testDirectory = dir;
|
|
571
|
+
break;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Also check src/__tests__
|
|
575
|
+
if (existsSync(join(projectPath, 'src', '__tests__'))) {
|
|
576
|
+
intelligence.testing.hasTests = true;
|
|
577
|
+
intelligence.testing.testDirectory = 'src/__tests__';
|
|
578
|
+
}
|
|
579
|
+
// Detect framework from config files or dependencies
|
|
580
|
+
if (existsSync(join(projectPath, 'jest.config.js')) ||
|
|
581
|
+
existsSync(join(projectPath, 'jest.config.ts')) ||
|
|
582
|
+
intelligence.dependencies.dev.includes('jest')) {
|
|
583
|
+
intelligence.testing.framework = 'Jest';
|
|
584
|
+
intelligence.testing.hasTests = true;
|
|
585
|
+
}
|
|
586
|
+
else if (existsSync(join(projectPath, 'vitest.config.ts')) ||
|
|
587
|
+
existsSync(join(projectPath, 'vitest.config.js')) ||
|
|
588
|
+
intelligence.dependencies.dev.includes('vitest')) {
|
|
589
|
+
intelligence.testing.framework = 'Vitest';
|
|
590
|
+
intelligence.testing.hasTests = true;
|
|
591
|
+
}
|
|
592
|
+
else if (existsSync(join(projectPath, 'pytest.ini')) ||
|
|
593
|
+
existsSync(join(projectPath, 'pyproject.toml'))) {
|
|
594
|
+
intelligence.testing.framework = 'Pytest';
|
|
595
|
+
intelligence.testing.hasTests = true;
|
|
596
|
+
}
|
|
597
|
+
else if (intelligence.dependencies.dev.includes('mocha')) {
|
|
598
|
+
intelligence.testing.framework = 'Mocha';
|
|
599
|
+
intelligence.testing.hasTests = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.127",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|