@claudetools/tools 0.1.0
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/README.md +86 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +61 -0
- package/dist/handlers/tool-handlers.d.ts +2 -0
- package/dist/handlers/tool-handlers.js +1108 -0
- package/dist/helpers/api-client.d.ts +50 -0
- package/dist/helpers/api-client.js +60 -0
- package/dist/helpers/config-manager.d.ts +68 -0
- package/dist/helpers/config-manager.js +306 -0
- package/dist/helpers/config.d.ts +55 -0
- package/dist/helpers/config.js +174 -0
- package/dist/helpers/dependencies.d.ts +30 -0
- package/dist/helpers/dependencies.js +87 -0
- package/dist/helpers/formatter.d.ts +2 -0
- package/dist/helpers/formatter.js +24 -0
- package/dist/helpers/patterns.d.ts +15 -0
- package/dist/helpers/patterns.js +118 -0
- package/dist/helpers/project-registration.d.ts +27 -0
- package/dist/helpers/project-registration.js +338 -0
- package/dist/helpers/tasks.d.ts +152 -0
- package/dist/helpers/tasks.js +274 -0
- package/dist/helpers/workers.d.ts +18 -0
- package/dist/helpers/workers.js +146 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +75 -0
- package/dist/logger.d.ts +32 -0
- package/dist/logger.js +401 -0
- package/dist/prompts.d.ts +2 -0
- package/dist/prompts.js +64 -0
- package/dist/resources.d.ts +2 -0
- package/dist/resources.js +79 -0
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +206 -0
- package/dist/tools.d.ts +2 -0
- package/dist/tools.js +748 -0
- package/package.json +58 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Configuration and Project ID Resolution
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { mcpLogger } from '../logger.js';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import { getConfig, getConfigDir, getProjectsPath, } from './config-manager.js';
|
|
7
|
+
import { getOrRegisterProject } from './project-registration.js';
|
|
8
|
+
// Configuration - now loaded from config manager
|
|
9
|
+
const config = getConfig();
|
|
10
|
+
export const API_BASE_URL = config.apiUrl;
|
|
11
|
+
export const CLAUDETOOLS_DIR = getConfigDir();
|
|
12
|
+
export const PROJECTS_FILE = getProjectsPath();
|
|
13
|
+
// Cache for resolved project ID
|
|
14
|
+
let resolvedProjectId = null;
|
|
15
|
+
/**
|
|
16
|
+
* Validates that a project ID is a proper UUID format
|
|
17
|
+
*/
|
|
18
|
+
export function isValidProjectId(id) {
|
|
19
|
+
return /^proj_[a-f0-9]{20}$/.test(id);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Finds a project binding in the cache by directory path
|
|
23
|
+
*/
|
|
24
|
+
export function findProjectBinding(dirPath) {
|
|
25
|
+
try {
|
|
26
|
+
if (!fs.existsSync(PROJECTS_FILE)) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const content = fs.readFileSync(PROJECTS_FILE, 'utf-8');
|
|
30
|
+
const cache = JSON.parse(content);
|
|
31
|
+
if (!cache?.bindings?.length) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Try exact match first
|
|
35
|
+
const exactMatch = cache.bindings.find(b => b.local_path === dirPath);
|
|
36
|
+
if (exactMatch) {
|
|
37
|
+
mcpLogger.debug('MEMORY', `UUID lookup exact match: ${exactMatch.project_id} for ${dirPath}`);
|
|
38
|
+
return exactMatch;
|
|
39
|
+
}
|
|
40
|
+
// Try prefix match (for subdirectories)
|
|
41
|
+
const prefixMatches = cache.bindings
|
|
42
|
+
.filter(b => dirPath.startsWith(b.local_path + '/') || dirPath === b.local_path)
|
|
43
|
+
.sort((a, b) => b.local_path.length - a.local_path.length);
|
|
44
|
+
if (prefixMatches.length > 0) {
|
|
45
|
+
mcpLogger.debug('MEMORY', `UUID lookup prefix match: ${prefixMatches[0].project_id} for ${dirPath}`);
|
|
46
|
+
return prefixMatches[0];
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
mcpLogger.debug('MEMORY', `Error reading projects cache: ${error}`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Look up project ID from local cache based on current working directory
|
|
57
|
+
* Synchronous version - throws if not already resolved
|
|
58
|
+
*/
|
|
59
|
+
export function resolveProjectId() {
|
|
60
|
+
// Return cached result if available
|
|
61
|
+
if (resolvedProjectId) {
|
|
62
|
+
return resolvedProjectId;
|
|
63
|
+
}
|
|
64
|
+
throw new Error('Project ID not yet resolved. Server must call resolveProjectIdAsync() during startup.');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Async version that auto-registers if needed
|
|
68
|
+
* Should be called during server startup
|
|
69
|
+
*/
|
|
70
|
+
export async function resolveProjectIdAsync() {
|
|
71
|
+
// Return cached result if available
|
|
72
|
+
if (resolvedProjectId) {
|
|
73
|
+
return resolvedProjectId;
|
|
74
|
+
}
|
|
75
|
+
// Check environment variable first
|
|
76
|
+
if (process.env.CLAUDETOOLS_PROJECT_ID) {
|
|
77
|
+
const envProjectId = process.env.CLAUDETOOLS_PROJECT_ID;
|
|
78
|
+
// Validate UUID format
|
|
79
|
+
if (!isValidProjectId(envProjectId)) {
|
|
80
|
+
throw new Error(`Invalid project ID format in CLAUDETOOLS_PROJECT_ID: ${envProjectId}. ` +
|
|
81
|
+
`Expected format: proj_xxxxxxxxxxxxxxxxxxxx (20 hex chars)`);
|
|
82
|
+
}
|
|
83
|
+
resolvedProjectId = envProjectId;
|
|
84
|
+
mcpLogger.debug('MEMORY', `Using project ID from env: ${resolvedProjectId}`);
|
|
85
|
+
return resolvedProjectId;
|
|
86
|
+
}
|
|
87
|
+
const cwd = process.cwd();
|
|
88
|
+
// Check projects.json cache
|
|
89
|
+
const binding = findProjectBinding(cwd);
|
|
90
|
+
if (binding) {
|
|
91
|
+
// Validate UUID format
|
|
92
|
+
if (!isValidProjectId(binding.project_id)) {
|
|
93
|
+
throw new Error(`Invalid project ID format in cache: ${binding.project_id}. ` +
|
|
94
|
+
`Cache may be corrupted. Delete ${PROJECTS_FILE} and restart.`);
|
|
95
|
+
}
|
|
96
|
+
resolvedProjectId = binding.project_id;
|
|
97
|
+
mcpLogger.info('MEMORY', `Project resolved from cache: ${resolvedProjectId}`);
|
|
98
|
+
return resolvedProjectId;
|
|
99
|
+
}
|
|
100
|
+
// No binding found - auto-register if enabled
|
|
101
|
+
if (config.autoRegister) {
|
|
102
|
+
mcpLogger.info('MEMORY', `No project binding found for ${cwd}, auto-registering...`);
|
|
103
|
+
try {
|
|
104
|
+
resolvedProjectId = await getOrRegisterProject(cwd);
|
|
105
|
+
mcpLogger.info('MEMORY', `Project auto-registered: ${resolvedProjectId}`);
|
|
106
|
+
return resolvedProjectId;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
mcpLogger.error('MEMORY', `Auto-registration failed: ${error}`);
|
|
110
|
+
throw new Error(`Failed to auto-register project for ${cwd}: ${error}\n\n` +
|
|
111
|
+
'Please check:\n' +
|
|
112
|
+
'1. CLAUDETOOLS_API_KEY or MEMORY_API_KEY is set\n' +
|
|
113
|
+
'2. API URL is correct in ~/.claudetools/config.json\n' +
|
|
114
|
+
'3. Network connectivity to ClaudeTools API');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Auto-register disabled - throw error
|
|
118
|
+
throw new Error(`No project binding found for ${cwd} and auto-registration is disabled.\n` +
|
|
119
|
+
`To register this project:\n` +
|
|
120
|
+
` 1. Set CLAUDETOOLS_PROJECT_ID environment variable with a valid proj_* UUID\n` +
|
|
121
|
+
` 2. Enable autoRegister in ~/.claudetools/config.json\n` +
|
|
122
|
+
` 3. Or manually register via API`);
|
|
123
|
+
}
|
|
124
|
+
// Lazy-loaded DEFAULT_PROJECT_ID to avoid startup errors
|
|
125
|
+
let _defaultProjectId = null;
|
|
126
|
+
/**
|
|
127
|
+
* Gets the default project ID (synchronous version)
|
|
128
|
+
* Throws if not already resolved - call resolveProjectIdAsync() first
|
|
129
|
+
*/
|
|
130
|
+
export function getDefaultProjectId() {
|
|
131
|
+
if (_defaultProjectId) {
|
|
132
|
+
return _defaultProjectId;
|
|
133
|
+
}
|
|
134
|
+
// Try config first
|
|
135
|
+
if (config.defaultProjectId) {
|
|
136
|
+
if (!isValidProjectId(config.defaultProjectId)) {
|
|
137
|
+
throw new Error(`Invalid defaultProjectId in config: ${config.defaultProjectId}. ` +
|
|
138
|
+
`Expected format: proj_xxxxxxxxxxxxxxxxxxxx (20 hex chars)`);
|
|
139
|
+
}
|
|
140
|
+
_defaultProjectId = config.defaultProjectId;
|
|
141
|
+
return _defaultProjectId;
|
|
142
|
+
}
|
|
143
|
+
// Fall back to resolveProjectId (throws if not resolved)
|
|
144
|
+
_defaultProjectId = resolveProjectId();
|
|
145
|
+
return _defaultProjectId;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Async version for server startup
|
|
149
|
+
*/
|
|
150
|
+
export async function getDefaultProjectIdAsync() {
|
|
151
|
+
if (_defaultProjectId) {
|
|
152
|
+
return _defaultProjectId;
|
|
153
|
+
}
|
|
154
|
+
// Try config first
|
|
155
|
+
if (config.defaultProjectId) {
|
|
156
|
+
if (!isValidProjectId(config.defaultProjectId)) {
|
|
157
|
+
throw new Error(`Invalid defaultProjectId in config: ${config.defaultProjectId}. ` +
|
|
158
|
+
`Expected format: proj_xxxxxxxxxxxxxxxxxxxx (20 hex chars)`);
|
|
159
|
+
}
|
|
160
|
+
_defaultProjectId = config.defaultProjectId;
|
|
161
|
+
return _defaultProjectId;
|
|
162
|
+
}
|
|
163
|
+
// Fall back to async resolve (may auto-register)
|
|
164
|
+
_defaultProjectId = await resolveProjectIdAsync();
|
|
165
|
+
return _defaultProjectId;
|
|
166
|
+
}
|
|
167
|
+
// Keep backward compatibility - but this will throw on access if no project bound
|
|
168
|
+
export const DEFAULT_USER_ID = config.defaultUserId || 'default-user';
|
|
169
|
+
export const AUTO_INJECT_CONTEXT = config.autoInjectContext;
|
|
170
|
+
// Store last context for explain_memory tool
|
|
171
|
+
export let lastContextUsed = null;
|
|
172
|
+
export function setLastContextUsed(context) {
|
|
173
|
+
lastContextUsed = context;
|
|
174
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ImpactResult {
|
|
2
|
+
function: string;
|
|
3
|
+
analysisType: string;
|
|
4
|
+
directCallers: {
|
|
5
|
+
function: string;
|
|
6
|
+
risk: string;
|
|
7
|
+
}[];
|
|
8
|
+
indirectCallers: {
|
|
9
|
+
function: string;
|
|
10
|
+
depth: number;
|
|
11
|
+
path: string[];
|
|
12
|
+
}[];
|
|
13
|
+
totalAffected: number;
|
|
14
|
+
riskLevel: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
15
|
+
recommendations: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function queryDependencies(projectId: string, functionName: string, direction: 'forward' | 'reverse' | 'both', depth?: number): Promise<{
|
|
18
|
+
function: string;
|
|
19
|
+
forward: {
|
|
20
|
+
function: string;
|
|
21
|
+
context: string;
|
|
22
|
+
fileName?: string;
|
|
23
|
+
}[];
|
|
24
|
+
reverse: {
|
|
25
|
+
function: string;
|
|
26
|
+
context: string;
|
|
27
|
+
fileName?: string;
|
|
28
|
+
}[];
|
|
29
|
+
}>;
|
|
30
|
+
export declare function analyzeImpact(projectId: string, functionName: string, analysisType?: 'change' | 'delete' | 'signature', maxDepth?: number): Promise<ImpactResult>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Dependency Analysis and Impact Assessment
|
|
3
|
+
// =============================================================================
|
|
4
|
+
import { searchMemory } from './api-client.js';
|
|
5
|
+
export async function queryDependencies(projectId, functionName, direction, depth = 1) {
|
|
6
|
+
const searchQuery = `${functionName} CALLS`;
|
|
7
|
+
const context = await searchMemory(projectId, searchQuery, 100);
|
|
8
|
+
const forward = [];
|
|
9
|
+
const reverse = [];
|
|
10
|
+
for (const fact of context.relevant_facts) {
|
|
11
|
+
if (fact.relation_type === 'CALLS') {
|
|
12
|
+
const caller = fact.source_name || '';
|
|
13
|
+
const called = fact.target_name || '';
|
|
14
|
+
if (caller === functionName && (direction === 'forward' || direction === 'both')) {
|
|
15
|
+
forward.push({ function: called, context: fact.fact });
|
|
16
|
+
}
|
|
17
|
+
if (called === functionName && (direction === 'reverse' || direction === 'both')) {
|
|
18
|
+
reverse.push({ function: caller, context: fact.fact });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { function: functionName, forward, reverse };
|
|
23
|
+
}
|
|
24
|
+
export async function analyzeImpact(projectId, functionName, analysisType = 'change', maxDepth = 3) {
|
|
25
|
+
const visited = new Set();
|
|
26
|
+
const directCallers = [];
|
|
27
|
+
const indirectCallers = [];
|
|
28
|
+
const directResult = await queryDependencies(projectId, functionName, 'reverse', 1);
|
|
29
|
+
for (const caller of directResult.reverse) {
|
|
30
|
+
visited.add(caller.function);
|
|
31
|
+
let risk = 'MEDIUM';
|
|
32
|
+
if (caller.function.includes('main') || caller.function.includes('init') || caller.function.includes('core')) {
|
|
33
|
+
risk = 'HIGH';
|
|
34
|
+
}
|
|
35
|
+
else if (caller.function.includes('test') || caller.function.includes('mock')) {
|
|
36
|
+
risk = 'LOW';
|
|
37
|
+
}
|
|
38
|
+
directCallers.push({ function: caller.function, risk });
|
|
39
|
+
}
|
|
40
|
+
if (maxDepth > 1) {
|
|
41
|
+
const queue = directCallers.map(c => ({ func: c.function, depth: 1, path: [functionName, c.function] }));
|
|
42
|
+
while (queue.length > 0) {
|
|
43
|
+
const current = queue.shift();
|
|
44
|
+
if (current.depth >= maxDepth)
|
|
45
|
+
continue;
|
|
46
|
+
const indirectResult = await queryDependencies(projectId, current.func, 'reverse', 1);
|
|
47
|
+
for (const caller of indirectResult.reverse) {
|
|
48
|
+
if (!visited.has(caller.function)) {
|
|
49
|
+
visited.add(caller.function);
|
|
50
|
+
const newPath = [...current.path, caller.function];
|
|
51
|
+
indirectCallers.push({
|
|
52
|
+
function: caller.function,
|
|
53
|
+
depth: current.depth + 1,
|
|
54
|
+
path: newPath,
|
|
55
|
+
});
|
|
56
|
+
queue.push({ func: caller.function, depth: current.depth + 1, path: newPath });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const totalAffected = directCallers.length + indirectCallers.length;
|
|
62
|
+
let riskLevel = 'LOW';
|
|
63
|
+
if (totalAffected > 20 || directCallers.some(c => c.risk === 'HIGH'))
|
|
64
|
+
riskLevel = 'CRITICAL';
|
|
65
|
+
else if (totalAffected > 10)
|
|
66
|
+
riskLevel = 'HIGH';
|
|
67
|
+
else if (totalAffected > 5)
|
|
68
|
+
riskLevel = 'MEDIUM';
|
|
69
|
+
const recommendations = [];
|
|
70
|
+
if (riskLevel === 'CRITICAL') {
|
|
71
|
+
recommendations.push('Create comprehensive test coverage before making changes');
|
|
72
|
+
recommendations.push('Consider deprecation strategy instead of immediate changes');
|
|
73
|
+
}
|
|
74
|
+
if (analysisType === 'signature') {
|
|
75
|
+
recommendations.push('Update all call sites simultaneously');
|
|
76
|
+
recommendations.push('Use TypeScript compiler to catch signature mismatches');
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
function: functionName,
|
|
80
|
+
analysisType,
|
|
81
|
+
directCallers,
|
|
82
|
+
indirectCallers,
|
|
83
|
+
totalAffected,
|
|
84
|
+
riskLevel,
|
|
85
|
+
recommendations,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Context Formatting for Claude
|
|
3
|
+
// =============================================================================
|
|
4
|
+
export function formatContextForClaude(context) {
|
|
5
|
+
if (!context.relevant_facts.length && !context.relevant_entities.length) {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
const parts = [];
|
|
9
|
+
if (context.relevant_entities.length > 0) {
|
|
10
|
+
parts.push('# Known Entities\n');
|
|
11
|
+
for (const entity of context.relevant_entities) {
|
|
12
|
+
const labels = entity.labels.join(', ');
|
|
13
|
+
parts.push(`- ${entity.name} (${labels})${entity.summary ? ': ' + entity.summary : ''}`);
|
|
14
|
+
}
|
|
15
|
+
parts.push('');
|
|
16
|
+
}
|
|
17
|
+
if (context.relevant_facts.length > 0) {
|
|
18
|
+
parts.push('# Relevant Facts\n');
|
|
19
|
+
for (const fact of context.relevant_facts) {
|
|
20
|
+
parts.push(`- ${fact.fact}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return parts.join('\n');
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface PatternWarning {
|
|
2
|
+
type: 'security' | 'performance';
|
|
3
|
+
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
4
|
+
pattern: string;
|
|
5
|
+
description: string;
|
|
6
|
+
line?: number;
|
|
7
|
+
recommendation: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PatternResult {
|
|
10
|
+
warnings: PatternWarning[];
|
|
11
|
+
securityScore: number;
|
|
12
|
+
performanceScore: number;
|
|
13
|
+
overallRisk: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
14
|
+
}
|
|
15
|
+
export declare function checkPatterns(code: string, checkTypes?: ('security' | 'performance' | 'all')[]): PatternResult;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Pattern Detection (Security and Performance)
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Security patterns to detect
|
|
5
|
+
const SECURITY_PATTERNS = [
|
|
6
|
+
{
|
|
7
|
+
pattern: /\beval\s*\(/i,
|
|
8
|
+
name: 'eval() usage',
|
|
9
|
+
description: 'eval() can execute arbitrary code and is a security risk',
|
|
10
|
+
severity: 'CRITICAL',
|
|
11
|
+
recommendation: 'Avoid eval(). Use JSON.parse() for JSON, or Function constructor for dynamic code',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
pattern: /innerHTML\s*=/i,
|
|
15
|
+
name: 'innerHTML assignment',
|
|
16
|
+
description: 'Direct innerHTML assignment can lead to XSS vulnerabilities',
|
|
17
|
+
severity: 'HIGH',
|
|
18
|
+
recommendation: 'Use textContent for text, or sanitize HTML with DOMPurify',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
pattern: /document\.write\s*\(/i,
|
|
22
|
+
name: 'document.write()',
|
|
23
|
+
description: 'document.write() can overwrite the entire document and enable XSS',
|
|
24
|
+
severity: 'HIGH',
|
|
25
|
+
recommendation: 'Use DOM methods like createElement() and appendChild()',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
pattern: /\bexec\s*\(/i,
|
|
29
|
+
name: 'exec() or shell command execution',
|
|
30
|
+
description: 'Executing shell commands can lead to command injection vulnerabilities',
|
|
31
|
+
severity: 'CRITICAL',
|
|
32
|
+
recommendation: 'Use safer alternatives, validate and sanitize all inputs',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
pattern: /process\.env\[/i,
|
|
36
|
+
name: 'Dynamic environment variable access',
|
|
37
|
+
description: 'Dynamic access to environment variables can expose sensitive data',
|
|
38
|
+
severity: 'MEDIUM',
|
|
39
|
+
recommendation: 'Use static environment variable names, validate values',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
const PERFORMANCE_PATTERNS = [
|
|
43
|
+
{
|
|
44
|
+
pattern: /for\s*\([^;]*;[^;]*;[^)]*\)\s*{[^}]*for\s*\(/i,
|
|
45
|
+
name: 'Nested loops',
|
|
46
|
+
description: 'Nested loops can cause O(n²) or worse performance',
|
|
47
|
+
severity: 'MEDIUM',
|
|
48
|
+
recommendation: 'Consider using maps, sets, or other data structures to reduce complexity',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
pattern: /\.push\(.*\)/g,
|
|
52
|
+
name: 'Array.push in loop',
|
|
53
|
+
description: 'Repeatedly pushing to arrays can cause memory reallocation',
|
|
54
|
+
severity: 'LOW',
|
|
55
|
+
recommendation: 'Pre-allocate arrays when size is known',
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
export function checkPatterns(code, checkTypes = ['all']) {
|
|
59
|
+
const warnings = [];
|
|
60
|
+
const lines = code.split('\n');
|
|
61
|
+
const shouldCheckSecurity = checkTypes.includes('all') || checkTypes.includes('security');
|
|
62
|
+
const shouldCheckPerformance = checkTypes.includes('all') || checkTypes.includes('performance');
|
|
63
|
+
if (shouldCheckSecurity) {
|
|
64
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
65
|
+
lines.forEach((line, index) => {
|
|
66
|
+
if (pattern.pattern.test(line)) {
|
|
67
|
+
warnings.push({
|
|
68
|
+
type: 'security',
|
|
69
|
+
severity: pattern.severity,
|
|
70
|
+
pattern: pattern.name,
|
|
71
|
+
description: pattern.description,
|
|
72
|
+
line: index + 1,
|
|
73
|
+
recommendation: pattern.recommendation,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (shouldCheckPerformance) {
|
|
80
|
+
for (const pattern of PERFORMANCE_PATTERNS) {
|
|
81
|
+
lines.forEach((line, index) => {
|
|
82
|
+
if (pattern.pattern.test(line)) {
|
|
83
|
+
warnings.push({
|
|
84
|
+
type: 'performance',
|
|
85
|
+
severity: pattern.severity,
|
|
86
|
+
pattern: pattern.name,
|
|
87
|
+
description: pattern.description,
|
|
88
|
+
line: index + 1,
|
|
89
|
+
recommendation: pattern.recommendation,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const securityWarnings = warnings.filter(w => w.type === 'security');
|
|
96
|
+
const performanceWarnings = warnings.filter(w => w.type === 'performance');
|
|
97
|
+
const securityScore = Math.max(0, 100 - (securityWarnings.length * 20));
|
|
98
|
+
const performanceScore = Math.max(0, 100 - (performanceWarnings.length * 10));
|
|
99
|
+
let overallRisk = 'LOW';
|
|
100
|
+
if (warnings.some(w => w.severity === 'CRITICAL')) {
|
|
101
|
+
overallRisk = 'CRITICAL';
|
|
102
|
+
}
|
|
103
|
+
else if (warnings.some(w => w.severity === 'HIGH')) {
|
|
104
|
+
overallRisk = 'HIGH';
|
|
105
|
+
}
|
|
106
|
+
else if (warnings.length > 5) {
|
|
107
|
+
overallRisk = 'HIGH';
|
|
108
|
+
}
|
|
109
|
+
else if (warnings.length > 2) {
|
|
110
|
+
overallRisk = 'MEDIUM';
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
warnings,
|
|
114
|
+
securityScore,
|
|
115
|
+
performanceScore,
|
|
116
|
+
overallRisk,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ProjectBinding {
|
|
2
|
+
binding_id: string;
|
|
3
|
+
project_id: string;
|
|
4
|
+
system_id: string;
|
|
5
|
+
local_path: string;
|
|
6
|
+
git_remote?: string;
|
|
7
|
+
project_name: string;
|
|
8
|
+
org_id: string;
|
|
9
|
+
cached_at: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ProjectsCache {
|
|
12
|
+
bindings: ProjectBinding[];
|
|
13
|
+
last_sync: string;
|
|
14
|
+
system_id?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get or register project for a local path
|
|
18
|
+
* This is the main function called by config.ts
|
|
19
|
+
*
|
|
20
|
+
* Returns the project_id (UUID) for the given path
|
|
21
|
+
*/
|
|
22
|
+
export declare function getOrRegisterProject(localPath: string): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Sync projects from API (for future use)
|
|
25
|
+
* This would fetch all bindings from the API and update local cache
|
|
26
|
+
*/
|
|
27
|
+
export declare function syncProjectsFromAPI(): Promise<void>;
|