@su-record/vibe 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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +448 -0
  3. package/agents/backend-python-expert.md +453 -0
  4. package/agents/database-postgres-expert.md +538 -0
  5. package/agents/frontend-flutter-expert.md +487 -0
  6. package/agents/frontend-react-expert.md +424 -0
  7. package/agents/quality-reviewer.md +542 -0
  8. package/agents/specification-agent.md +505 -0
  9. package/bin/sutory +332 -0
  10. package/bin/vibe +338 -0
  11. package/mcp/dist/__tests__/complexity.test.js +126 -0
  12. package/mcp/dist/__tests__/memory.test.js +120 -0
  13. package/mcp/dist/__tests__/python-dart-complexity.test.js +146 -0
  14. package/mcp/dist/index.js +230 -0
  15. package/mcp/dist/lib/ContextCompressor.js +305 -0
  16. package/mcp/dist/lib/MemoryManager.js +334 -0
  17. package/mcp/dist/lib/ProjectCache.js +126 -0
  18. package/mcp/dist/lib/PythonParser.js +241 -0
  19. package/mcp/dist/tools/browser/browserPool.js +76 -0
  20. package/mcp/dist/tools/browser/browserUtils.js +135 -0
  21. package/mcp/dist/tools/browser/inspectNetworkRequests.js +140 -0
  22. package/mcp/dist/tools/browser/monitorConsoleLogs.js +97 -0
  23. package/mcp/dist/tools/convention/analyzeComplexity.js +248 -0
  24. package/mcp/dist/tools/convention/applyQualityRules.js +102 -0
  25. package/mcp/dist/tools/convention/checkCouplingCohesion.js +233 -0
  26. package/mcp/dist/tools/convention/complexityMetrics.js +133 -0
  27. package/mcp/dist/tools/convention/dartComplexity.js +117 -0
  28. package/mcp/dist/tools/convention/getCodingGuide.js +64 -0
  29. package/mcp/dist/tools/convention/languageDetector.js +50 -0
  30. package/mcp/dist/tools/convention/pythonComplexity.js +109 -0
  31. package/mcp/dist/tools/convention/suggestImprovements.js +257 -0
  32. package/mcp/dist/tools/convention/validateCodeQuality.js +177 -0
  33. package/mcp/dist/tools/memory/autoSaveContext.js +79 -0
  34. package/mcp/dist/tools/memory/database.js +123 -0
  35. package/mcp/dist/tools/memory/deleteMemory.js +39 -0
  36. package/mcp/dist/tools/memory/listMemories.js +38 -0
  37. package/mcp/dist/tools/memory/memoryConfig.js +27 -0
  38. package/mcp/dist/tools/memory/memorySQLite.js +138 -0
  39. package/mcp/dist/tools/memory/memoryUtils.js +34 -0
  40. package/mcp/dist/tools/memory/migrate.js +113 -0
  41. package/mcp/dist/tools/memory/prioritizeMemory.js +109 -0
  42. package/mcp/dist/tools/memory/recallMemory.js +40 -0
  43. package/mcp/dist/tools/memory/restoreSessionContext.js +69 -0
  44. package/mcp/dist/tools/memory/saveMemory.js +34 -0
  45. package/mcp/dist/tools/memory/searchMemories.js +37 -0
  46. package/mcp/dist/tools/memory/startSession.js +100 -0
  47. package/mcp/dist/tools/memory/updateMemory.js +46 -0
  48. package/mcp/dist/tools/planning/analyzeRequirements.js +166 -0
  49. package/mcp/dist/tools/planning/createUserStories.js +119 -0
  50. package/mcp/dist/tools/planning/featureRoadmap.js +202 -0
  51. package/mcp/dist/tools/planning/generatePrd.js +156 -0
  52. package/mcp/dist/tools/prompt/analyzePrompt.js +145 -0
  53. package/mcp/dist/tools/prompt/enhancePrompt.js +105 -0
  54. package/mcp/dist/tools/semantic/findReferences.js +195 -0
  55. package/mcp/dist/tools/semantic/findSymbol.js +200 -0
  56. package/mcp/dist/tools/thinking/analyzeProblem.js +50 -0
  57. package/mcp/dist/tools/thinking/breakDownProblem.js +140 -0
  58. package/mcp/dist/tools/thinking/createThinkingChain.js +39 -0
  59. package/mcp/dist/tools/thinking/formatAsPlan.js +73 -0
  60. package/mcp/dist/tools/thinking/stepByStepAnalysis.js +58 -0
  61. package/mcp/dist/tools/thinking/thinkAloudProcess.js +75 -0
  62. package/mcp/dist/tools/time/getCurrentTime.js +61 -0
  63. package/mcp/dist/tools/ui/previewUiAscii.js +232 -0
  64. package/mcp/dist/types/tool.js +2 -0
  65. package/mcp/package.json +53 -0
  66. package/package.json +49 -0
  67. package/scripts/install-mcp.js +48 -0
  68. package/scripts/install.sh +70 -0
  69. package/skills/core/communication-guide.md +104 -0
  70. package/skills/core/development-philosophy.md +53 -0
  71. package/skills/core/quick-start.md +121 -0
  72. package/skills/languages/dart-flutter.md +509 -0
  73. package/skills/languages/python-fastapi.md +386 -0
  74. package/skills/languages/typescript-nextjs.md +441 -0
  75. package/skills/languages/typescript-react-native.md +446 -0
  76. package/skills/languages/typescript-react.md +525 -0
  77. package/skills/quality/checklist.md +276 -0
  78. package/skills/quality/testing-strategy.md +437 -0
  79. package/skills/standards/anti-patterns.md +369 -0
  80. package/skills/standards/code-structure.md +291 -0
  81. package/skills/standards/complexity-metrics.md +312 -0
  82. package/skills/standards/naming-conventions.md +198 -0
  83. package/skills/tools/mcp-hi-ai-guide.md +665 -0
  84. package/skills/tools/mcp-workflow.md +51 -0
  85. package/templates/constitution-template.md +193 -0
  86. package/templates/plan-template.md +237 -0
  87. package/templates/spec-template.md +142 -0
  88. package/templates/tasks-template.md +132 -0
@@ -0,0 +1,140 @@
1
+ // Browser development tool - completely independent
2
+ import puppeteer from 'puppeteer-core';
3
+ import { getBrowserLaunchOptions } from './browserUtils.js';
4
+ export const inspectNetworkRequestsDefinition = {
5
+ name: 'inspect_network_requests',
6
+ description: '네트워크|API 호출|요청 확인|network|API calls|check requests|network traffic - Inspect network requests',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ url: { type: 'string', description: 'URL to inspect' },
11
+ filterType: { type: 'string', description: 'Request type filter', enum: ['all', 'xhr', 'fetch', 'websocket', 'failed'] },
12
+ includeHeaders: { type: 'boolean', description: 'Include request/response headers' }
13
+ },
14
+ required: ['url']
15
+ },
16
+ annotations: {
17
+ title: 'Inspect Network Requests',
18
+ audience: ['user', 'assistant']
19
+ }
20
+ };
21
+ export async function inspectNetworkRequests(args) {
22
+ const { url: inspectUrl, filterType = 'all', includeHeaders = false } = args;
23
+ try {
24
+ // Get browser launch options with proper executable path
25
+ const launchOptions = getBrowserLaunchOptions();
26
+ const browser = await puppeteer.launch(launchOptions);
27
+ const page = await browser.newPage();
28
+ const networkRequests = [];
29
+ let requestId = 0;
30
+ const requestTimings = new Map();
31
+ // Capture network requests
32
+ page.on('request', request => {
33
+ const startTime = Date.now();
34
+ const id = `req_${String(requestId++).padStart(3, '0')}`;
35
+ const requestUrl = request.url();
36
+ requestTimings.set(requestUrl, startTime);
37
+ networkRequests.push({
38
+ id,
39
+ url: requestUrl,
40
+ method: request.method(),
41
+ type: request.resourceType(),
42
+ responseTime: 0,
43
+ size: 0,
44
+ timestamp: new Date().toISOString(),
45
+ headers: includeHeaders ? {
46
+ request: request.headers()
47
+ } : undefined
48
+ });
49
+ });
50
+ page.on('response', async (response) => {
51
+ const requestUrl = response.url();
52
+ const request = networkRequests.find(req => req.url === requestUrl);
53
+ const startTime = requestTimings.get(requestUrl);
54
+ if (request) {
55
+ request.status = response.status();
56
+ request.statusText = response.statusText();
57
+ request.responseTime = startTime ? Date.now() - startTime : 0;
58
+ request.failed = !response.ok();
59
+ if (includeHeaders && request.headers) {
60
+ request.headers.response = response.headers();
61
+ }
62
+ // Estimate response size
63
+ try {
64
+ const buffer = await response.buffer();
65
+ request.size = buffer.length;
66
+ }
67
+ catch {
68
+ request.size = 0;
69
+ }
70
+ }
71
+ });
72
+ page.on('requestfailed', request => {
73
+ const requestUrl = request.url();
74
+ const failedRequest = networkRequests.find(req => req.url === requestUrl);
75
+ if (failedRequest) {
76
+ failedRequest.failed = true;
77
+ failedRequest.status = 0;
78
+ failedRequest.statusText = request.failure()?.errorText || 'Failed';
79
+ }
80
+ });
81
+ // Navigate to URL and wait for network to be idle
82
+ await page.goto(inspectUrl, { waitUntil: 'networkidle0', timeout: 30000 });
83
+ // Wait a bit for any remaining requests
84
+ await new Promise(resolve => setTimeout(resolve, 2000));
85
+ await browser.close();
86
+ const filteredRequests = networkRequests.filter(req => {
87
+ switch (filterType) {
88
+ case 'xhr':
89
+ return req.type === 'xhr';
90
+ case 'fetch':
91
+ return req.type === 'fetch';
92
+ case 'websocket':
93
+ return req.type === 'websocket';
94
+ case 'failed':
95
+ return req.failed || (req.status !== undefined && req.status >= 400);
96
+ default:
97
+ return true;
98
+ }
99
+ });
100
+ const networkInspectionResult = {
101
+ action: 'inspect_network_requests',
102
+ url: inspectUrl,
103
+ filterType,
104
+ includeHeaders,
105
+ requests: filteredRequests,
106
+ summary: {
107
+ totalRequests: filteredRequests.length,
108
+ successful: filteredRequests.filter(r => r.status !== undefined && r.status >= 200 && r.status < 300).length,
109
+ failed: filteredRequests.filter(r => r.failed || (r.status !== undefined && r.status >= 400)).length,
110
+ averageResponseTime: filteredRequests.reduce((sum, r) => sum + r.responseTime, 0) / filteredRequests.length,
111
+ totalDataTransferred: filteredRequests.reduce((sum, r) => sum + r.size, 0),
112
+ requestTypes: {
113
+ xhr: filteredRequests.filter(r => r.type === 'xhr').length,
114
+ fetch: filteredRequests.filter(r => r.type === 'fetch').length,
115
+ websocket: filteredRequests.filter(r => r.type === 'websocket').length
116
+ }
117
+ },
118
+ status: 'success'
119
+ };
120
+ // Compact summary format
121
+ const failed = filteredRequests.filter(r => r.failed || (r.status !== undefined && r.status >= 400));
122
+ const errorSummary = failed.length > 0
123
+ ? `\nErrors: ${failed.slice(0, 3).map(r => `${r.method} ${new URL(r.url).pathname} (${r.status})`).join(', ')}${failed.length > 3 ? ` +${failed.length - 3}` : ''}`
124
+ : '';
125
+ return {
126
+ content: [{
127
+ type: 'text',
128
+ text: `${networkInspectionResult.summary.totalRequests} reqs | ${networkInspectionResult.summary.successful} OK, ${networkInspectionResult.summary.failed} fail | Avg: ${networkInspectionResult.summary.averageResponseTime.toFixed(0)}ms | ${(networkInspectionResult.summary.totalDataTransferred / 1024).toFixed(1)}KB${errorSummary}`
129
+ }]
130
+ };
131
+ }
132
+ catch (error) {
133
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
134
+ const helpMessage = errorMessage.includes('Chrome') ?
135
+ '\n\nTroubleshooting:\n1. Install Chrome: https://www.google.com/chrome/\n2. Or set CHROME_PATH environment variable\n3. Or install puppeteer instead of puppeteer-core' : '';
136
+ return {
137
+ content: [{ type: 'text', text: `Error inspecting network requests: ${errorMessage}${helpMessage}` }]
138
+ };
139
+ }
140
+ }
@@ -0,0 +1,97 @@
1
+ // Browser development tool - completely independent
2
+ import puppeteer from 'puppeteer-core';
3
+ import { getBrowserLaunchOptions } from './browserUtils.js';
4
+ export const monitorConsoleLogsDefinition = {
5
+ name: 'monitor_console_logs',
6
+ description: '콘솔 로그|에러 확인|로그 봐줘|console|check logs|debug output|console errors - Monitor browser console',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ url: { type: 'string', description: 'URL to monitor' },
11
+ logLevel: { type: 'string', description: 'Log level to capture', enum: ['all', 'error', 'warn', 'info', 'debug'] },
12
+ duration: { type: 'number', description: 'Monitoring duration in seconds' }
13
+ },
14
+ required: ['url']
15
+ },
16
+ annotations: {
17
+ title: 'Monitor Console Logs',
18
+ audience: ['user', 'assistant']
19
+ }
20
+ };
21
+ export async function monitorConsoleLogs(args) {
22
+ const { url: monitorUrl, logLevel = 'all', duration = 30 } = args;
23
+ try {
24
+ // Get browser launch options with proper executable path
25
+ const launchOptions = getBrowserLaunchOptions();
26
+ const browser = await puppeteer.launch(launchOptions);
27
+ const page = await browser.newPage();
28
+ const logs = [];
29
+ // Capture console events
30
+ page.on('console', msg => {
31
+ const msgLevel = msg.type();
32
+ if (logLevel === 'all' || msgLevel === logLevel) {
33
+ logs.push({
34
+ timestamp: new Date().toISOString(),
35
+ level: msgLevel,
36
+ message: msg.text(),
37
+ source: msg.location()?.url || 'unknown'
38
+ });
39
+ }
40
+ });
41
+ // Capture page errors
42
+ page.on('pageerror', error => {
43
+ if (logLevel === 'all' || logLevel === 'error') {
44
+ logs.push({
45
+ timestamp: new Date().toISOString(),
46
+ level: 'error',
47
+ message: error.message,
48
+ source: error.stack?.split('\n')[0] || 'unknown'
49
+ });
50
+ }
51
+ });
52
+ // Navigate to URL and wait for specified duration
53
+ await page.goto(monitorUrl, { waitUntil: 'networkidle0', timeout: 30000 });
54
+ await new Promise(resolve => setTimeout(resolve, duration * 1000));
55
+ await browser.close();
56
+ const consoleMonitorResult = {
57
+ action: 'monitor_console_logs',
58
+ url: monitorUrl,
59
+ logLevel,
60
+ duration,
61
+ capturedLogs: logs,
62
+ summary: {
63
+ totalLogs: logs.length,
64
+ errors: logs.filter(l => l.level === 'error').length,
65
+ warnings: logs.filter(l => l.level === 'warn').length,
66
+ infos: logs.filter(l => l.level === 'info').length,
67
+ debugs: logs.filter(l => l.level === 'debug').length,
68
+ logs: logs.filter(l => l.level === 'log').length
69
+ },
70
+ monitoringStatus: 'completed',
71
+ status: 'success'
72
+ };
73
+ // Compact summary with errors only
74
+ const errors = logs.filter(l => l.level === 'error');
75
+ const warnings = logs.filter(l => l.level === 'warn');
76
+ const errorSummary = errors.length > 0
77
+ ? `\nErrors: ${errors.slice(0, 3).map(l => l.message.substring(0, 50)).join(', ')}${errors.length > 3 ? ` +${errors.length - 3}` : ''}`
78
+ : '';
79
+ const warnSummary = warnings.length > 0 && errors.length === 0
80
+ ? `\nWarnings: ${warnings.slice(0, 3).map(l => l.message.substring(0, 50)).join(', ')}${warnings.length > 3 ? ` +${warnings.length - 3}` : ''}`
81
+ : '';
82
+ return {
83
+ content: [{
84
+ type: 'text',
85
+ text: `${logs.length} logs | ${consoleMonitorResult.summary.errors}E ${consoleMonitorResult.summary.warnings}W ${consoleMonitorResult.summary.infos}I | ${duration}s${errorSummary}${warnSummary}`
86
+ }]
87
+ };
88
+ }
89
+ catch (error) {
90
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
91
+ const helpMessage = errorMessage.includes('Chrome') ?
92
+ '\n\nTroubleshooting:\n1. Install Chrome: https://www.google.com/chrome/\n2. Or set CHROME_PATH environment variable\n3. Or install puppeteer instead of puppeteer-core' : '';
93
+ return {
94
+ content: [{ type: 'text', text: `Error monitoring console logs: ${errorMessage}${helpMessage}` }]
95
+ };
96
+ }
97
+ }
@@ -0,0 +1,248 @@
1
+ // Convention management tool - completely independent
2
+ import { Project, ScriptKind } from "ts-morph";
3
+ import { PythonParser } from '../../lib/PythonParser.js';
4
+ // Reusable in-memory project to avoid re-parsing standard lib every call
5
+ const AST_PROJECT = new Project({
6
+ useInMemoryFileSystem: true,
7
+ compilerOptions: { allowJs: true, skipLibCheck: true }
8
+ });
9
+ // Enhanced Software Engineering Metrics
10
+ const CODE_QUALITY_METRICS = {
11
+ COMPLEXITY: {
12
+ maxCyclomaticComplexity: 10,
13
+ maxCognitiveComplexity: 15,
14
+ maxFunctionLines: 20,
15
+ maxNestingDepth: 3,
16
+ maxParameters: 5
17
+ },
18
+ COUPLING: {
19
+ maxDependencies: 7,
20
+ maxFanOut: 5,
21
+ preventCircularDeps: true
22
+ },
23
+ COHESION: {
24
+ singleResponsibility: true,
25
+ relatedFunctionsOnly: true
26
+ },
27
+ MAINTAINABILITY: {
28
+ noMagicNumbers: true,
29
+ consistentNaming: true,
30
+ properErrorHandling: true,
31
+ typesSafety: true
32
+ },
33
+ PERFORMANCE: {
34
+ memoizeExpensiveCalc: true,
35
+ lazyLoading: true,
36
+ batchOperations: true
37
+ }
38
+ };
39
+ export const analyzeComplexityDefinition = {
40
+ name: 'analyze_complexity',
41
+ description: '복잡도|복잡한지|complexity|how complex|난이도 - Analyze code complexity',
42
+ inputSchema: {
43
+ type: 'object',
44
+ properties: {
45
+ code: { type: 'string', description: 'Code to analyze' },
46
+ metrics: { type: 'string', description: 'Metrics to calculate', enum: ['cyclomatic', 'cognitive', 'halstead', 'all'] }
47
+ },
48
+ required: ['code']
49
+ },
50
+ annotations: {
51
+ title: 'Analyze Complexity',
52
+ audience: ['user', 'assistant']
53
+ }
54
+ };
55
+ /**
56
+ * Calculate cognitive complexity (how hard code is to understand)
57
+ */
58
+ function calculateCognitiveComplexity(code) {
59
+ const CONTROL_STRUCTURES = ['if', 'for', 'while'];
60
+ let cognitiveScore = 0;
61
+ const lines = code.split('\n');
62
+ let nestingLevel = 0;
63
+ for (const line of lines) {
64
+ const trimmed = line.trim();
65
+ // Increment for control structures
66
+ if (CONTROL_STRUCTURES.some(keyword => trimmed.includes(keyword))) {
67
+ cognitiveScore += 1 + nestingLevel;
68
+ }
69
+ // Increment for catch/switch
70
+ if (trimmed.includes('catch') || trimmed.includes('switch')) {
71
+ cognitiveScore += 1 + nestingLevel;
72
+ }
73
+ // Update nesting level
74
+ const openBraces = (line.match(/\{/g) || []).length;
75
+ const closeBraces = (line.match(/\}/g) || []).length;
76
+ nestingLevel = Math.max(0, nestingLevel + openBraces - closeBraces);
77
+ }
78
+ const threshold = CODE_QUALITY_METRICS.COMPLEXITY.maxCognitiveComplexity;
79
+ return {
80
+ value: cognitiveScore,
81
+ threshold,
82
+ status: cognitiveScore <= threshold ? 'pass' : 'fail',
83
+ description: 'How difficult the code is to understand'
84
+ };
85
+ }
86
+ /**
87
+ * Calculate AST-based cyclomatic complexity
88
+ */
89
+ function calculateAstComplexity(code) {
90
+ const CONTROL_FLOW_NODES = [
91
+ 'IfStatement', 'ForStatement', 'ForOfStatement', 'ForInStatement',
92
+ 'WhileStatement', 'CaseClause', 'ConditionalExpression',
93
+ 'DoStatement', 'CatchClause', 'BinaryExpression'
94
+ ];
95
+ let astCyclomatic = 1;
96
+ try {
97
+ const sourceFile = AST_PROJECT.createSourceFile('temp.ts', code, {
98
+ overwrite: true,
99
+ scriptKind: ScriptKind.TS
100
+ });
101
+ sourceFile.forEachDescendant((node) => {
102
+ if (CONTROL_FLOW_NODES.includes(node.getKindName())) {
103
+ astCyclomatic++;
104
+ }
105
+ });
106
+ const threshold = CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity;
107
+ return {
108
+ value: astCyclomatic,
109
+ threshold,
110
+ status: astCyclomatic <= threshold ? 'pass' : 'fail',
111
+ description: 'AST 기반 분기/조건문 수를 통한 cyclomatic complexity'
112
+ };
113
+ }
114
+ catch (e) {
115
+ return {
116
+ value: null,
117
+ status: 'error',
118
+ description: 'AST 분석 실패: ' + (e instanceof Error ? e.message : String(e))
119
+ };
120
+ }
121
+ }
122
+ /**
123
+ * Analyze Python code complexity
124
+ */
125
+ async function analyzePythonComplexity(code) {
126
+ try {
127
+ const pythonComplexity = await PythonParser.analyzeComplexity(code);
128
+ const totalComplexity = pythonComplexity.cyclomaticComplexity;
129
+ const issues = [];
130
+ const MAX_COMPLEXITY = 10;
131
+ if (totalComplexity > MAX_COMPLEXITY) {
132
+ issues.push('High complexity');
133
+ }
134
+ pythonComplexity.functions.forEach(f => {
135
+ if (f.complexity > MAX_COMPLEXITY) {
136
+ issues.push(`Function ${f.name}: complexity ${f.complexity}`);
137
+ }
138
+ });
139
+ const issuesText = issues.length ? `\nIssues: ${issues.join(', ')}` : '';
140
+ return {
141
+ content: [{
142
+ type: 'text',
143
+ text: `Python Complexity: ${totalComplexity}\nFunctions: ${pythonComplexity.functions.length}\nClasses: ${pythonComplexity.classes.length}${issuesText}`
144
+ }]
145
+ };
146
+ }
147
+ catch (error) {
148
+ return {
149
+ content: [{
150
+ type: 'text',
151
+ text: `Python analysis error: ${error instanceof Error ? error.message : 'Unknown error'}`
152
+ }]
153
+ };
154
+ }
155
+ }
156
+ export async function analyzeComplexity(args) {
157
+ const { code: complexityCode, metrics: complexityMetrics = 'all' } = args;
158
+ // Check if this is Python code
159
+ if (PythonParser.isPythonCode(complexityCode)) {
160
+ return analyzePythonComplexity(complexityCode);
161
+ }
162
+ const complexityAnalysis = {
163
+ action: 'analyze_complexity',
164
+ metrics: complexityMetrics,
165
+ results: {},
166
+ overallScore: 0,
167
+ issues: [],
168
+ recommendations: [],
169
+ status: 'pending'
170
+ };
171
+ // AST 기반 cyclomatic complexity 분석
172
+ complexityAnalysis.results.astCyclomaticComplexity = calculateAstComplexity(complexityCode);
173
+ if (complexityMetrics === 'cyclomatic' || complexityMetrics === 'all') {
174
+ const cyclomaticComplexityScore = (complexityCode.match(/\bif\b|\bfor\b|\bwhile\b|\bcase\b|\b&&\b|\b\|\|\b/g) || []).length + 1;
175
+ complexityAnalysis.results.cyclomaticComplexity = {
176
+ value: cyclomaticComplexityScore,
177
+ threshold: CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity,
178
+ status: cyclomaticComplexityScore <= CODE_QUALITY_METRICS.COMPLEXITY.maxCyclomaticComplexity ? 'pass' : 'fail',
179
+ description: 'Number of linearly independent paths through the code'
180
+ };
181
+ }
182
+ if (complexityMetrics === 'cognitive' || complexityMetrics === 'all') {
183
+ complexityAnalysis.results.cognitiveComplexity = calculateCognitiveComplexity(complexityCode);
184
+ }
185
+ if (complexityMetrics === 'halstead' || complexityMetrics === 'all') {
186
+ // Halstead metrics calculation (simplified version)
187
+ const operators = (complexityCode.match(/[+\-*/=<>!&|%^~?:]/g) || []).length;
188
+ const operands = (complexityCode.match(/\b[a-zA-Z_]\w*\b/g) || []).length;
189
+ const uniqueOperators = new Set(complexityCode.match(/[+\-*/=<>!&|%^~?:]/g) || []).size;
190
+ const uniqueOperands = new Set(complexityCode.match(/\b[a-zA-Z_]\w*\b/g) || []).size;
191
+ const vocabulary = uniqueOperators + uniqueOperands;
192
+ const length = operators + operands;
193
+ const calculatedLength = vocabulary > 0 ? uniqueOperators * Math.log2(uniqueOperators) + uniqueOperands * Math.log2(uniqueOperands) : 0;
194
+ const volume = length * Math.log2(vocabulary);
195
+ const difficulty = vocabulary > 0 ? (uniqueOperators / 2) * (operands / uniqueOperands) : 0;
196
+ const effort = difficulty * volume;
197
+ complexityAnalysis.results.halsteadMetrics = {
198
+ vocabulary: vocabulary,
199
+ length: length,
200
+ calculatedLength: Math.round(calculatedLength),
201
+ volume: Math.round(volume),
202
+ difficulty: Math.round(difficulty * 100) / 100,
203
+ effort: Math.round(effort),
204
+ timeToProgram: Math.round(effort / 18), // Halstead's formula: effort / 18 seconds
205
+ bugsDelivered: Math.round(volume / 3000 * 100) / 100, // Halstead's formula: volume / 3000
206
+ description: 'Software science metrics measuring program complexity'
207
+ };
208
+ }
209
+ // Additional complexity metrics
210
+ if (complexityMetrics === 'all') {
211
+ const lines = complexityCode.split('\n');
212
+ const nonEmptyLines = lines.filter(line => line.trim().length > 0).length;
213
+ const comments = (complexityCode.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length;
214
+ const functions = (complexityCode.match(/function\s+\w+|\w+\s*=\s*\(/g) || []).length;
215
+ const classes = (complexityCode.match(/class\s+\w+/g) || []).length;
216
+ complexityAnalysis.results.additionalMetrics = {
217
+ linesOfCode: nonEmptyLines,
218
+ comments: comments,
219
+ commentRatio: nonEmptyLines > 0 ? Math.round((comments / nonEmptyLines) * 100) / 100 : 0,
220
+ functions: functions,
221
+ classes: classes,
222
+ averageFunctionLength: functions > 0 ? Math.round(nonEmptyLines / functions) : 0
223
+ };
224
+ }
225
+ // Overall assessment
226
+ const issues = [];
227
+ let overallScore = 100;
228
+ if (complexityAnalysis.results.cyclomaticComplexity && complexityAnalysis.results.cyclomaticComplexity.status === 'fail') {
229
+ issues.push('High cyclomatic complexity detected');
230
+ overallScore -= 20;
231
+ }
232
+ if (complexityAnalysis.results.cognitiveComplexity && complexityAnalysis.results.cognitiveComplexity.status === 'fail') {
233
+ issues.push('High cognitive complexity detected');
234
+ overallScore -= 25;
235
+ }
236
+ if (complexityAnalysis.results.halsteadMetrics && complexityAnalysis.results.halsteadMetrics.difficulty > 10) {
237
+ issues.push('High Halstead difficulty detected');
238
+ overallScore -= 15;
239
+ }
240
+ complexityAnalysis.overallScore = Math.max(0, overallScore);
241
+ complexityAnalysis.issues = issues;
242
+ return {
243
+ content: [{
244
+ type: 'text',
245
+ text: `Complexity: ${complexityAnalysis.results.astCyclomaticComplexity?.value ?? 'N/A'}\nScore: ${complexityAnalysis.overallScore}${issues.length ? '\nIssues: ' + issues.join(', ') : ''}`
246
+ }]
247
+ };
248
+ }
@@ -0,0 +1,102 @@
1
+ // Convention management tool - completely independent
2
+ // Code Quality Standards
3
+ const QUALITY_RULES = {
4
+ NAMING: {
5
+ variables: 'nouns (userList, userData)',
6
+ functions: 'verb+noun (fetchData, updateUser)',
7
+ events: 'handle prefix (handleClick, handleSubmit)',
8
+ booleans: 'is/has/can prefix (isLoading, hasError, canEdit)',
9
+ constants: 'UPPER_SNAKE_CASE (MAX_RETRY_COUNT, API_TIMEOUT)',
10
+ components: 'PascalCase (UserProfile, HeaderSection)',
11
+ hooks: 'use prefix (useUserData, useAuth)'
12
+ },
13
+ STRUCTURE: {
14
+ componentOrder: ['State & Refs', 'Custom Hooks', 'Event Handlers', 'Effects', 'Early returns', 'Main return JSX'],
15
+ functionMaxLines: 20,
16
+ componentMaxLines: 50,
17
+ maxNestingDepth: 3
18
+ },
19
+ ANTIPATTERNS: {
20
+ typescript: ['any type usage', '@ts-ignore usage', 'as any casting'],
21
+ react: ['dangerouslySetInnerHTML', 'props drilling (3+ levels)'],
22
+ javascript: ['var usage', '== instead of ===', 'eval() usage'],
23
+ css: ['!important abuse', 'inline style abuse']
24
+ },
25
+ ASYNC_STATES: ['data', 'isLoading', 'error'],
26
+ STATE_MANAGEMENT: {
27
+ simple: 'useState',
28
+ complex: 'useReducer',
29
+ globalUI: 'Context API',
30
+ globalApp: 'Zustand',
31
+ server: 'TanStack Query'
32
+ }
33
+ };
34
+ export const applyQualityRulesDefinition = {
35
+ name: 'apply_quality_rules',
36
+ description: '규칙 적용|표준 적용|apply rules|apply standards|follow conventions|적용해 - Apply quality rules',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ scope: { type: 'string', description: 'Application scope', enum: ['naming', 'structure', 'typescript', 'react', 'accessibility', 'all'] },
41
+ language: { type: 'string', description: 'Programming language context', enum: ['javascript', 'typescript', 'react', 'vue', 'general'] }
42
+ },
43
+ required: ['scope']
44
+ },
45
+ annotations: {
46
+ title: 'Apply Quality Rules',
47
+ audience: ['user', 'assistant']
48
+ }
49
+ };
50
+ export async function applyQualityRules(args) {
51
+ const { scope, language: contextLanguage = 'general' } = args;
52
+ const applicableRules = [];
53
+ if (scope === 'naming' || scope === 'all') {
54
+ applicableRules.push({
55
+ category: 'Naming Conventions',
56
+ rules: QUALITY_RULES.NAMING
57
+ });
58
+ }
59
+ if (scope === 'structure' || scope === 'all') {
60
+ applicableRules.push({
61
+ category: 'Code Structure',
62
+ rules: QUALITY_RULES.STRUCTURE
63
+ });
64
+ }
65
+ if (scope === 'typescript' || scope === 'all') {
66
+ applicableRules.push({
67
+ category: 'TypeScript Guidelines',
68
+ rules: QUALITY_RULES.ANTIPATTERNS.typescript
69
+ });
70
+ }
71
+ if (scope === 'react' || scope === 'all') {
72
+ applicableRules.push({
73
+ category: 'React Guidelines',
74
+ rules: QUALITY_RULES.ANTIPATTERNS.react
75
+ });
76
+ }
77
+ if (scope === 'accessibility' || scope === 'all') {
78
+ applicableRules.push({
79
+ category: 'Accessibility Guidelines',
80
+ rules: [
81
+ 'Use semantic HTML elements',
82
+ 'Provide alt text for images',
83
+ 'Ensure keyboard navigation',
84
+ 'Maintain color contrast ratios',
85
+ 'Use ARIA labels when needed'
86
+ ]
87
+ });
88
+ }
89
+ const qualityRulesResult = {
90
+ action: 'apply_quality_rules',
91
+ scope,
92
+ language: contextLanguage,
93
+ rules: applicableRules,
94
+ asyncStates: QUALITY_RULES.ASYNC_STATES,
95
+ stateManagement: QUALITY_RULES.STATE_MANAGEMENT,
96
+ status: 'success'
97
+ };
98
+ const rulesSummary = applicableRules.map(r => `${r.category}: ${Array.isArray(r.rules) ? r.rules.length + ' rules' : Object.keys(r.rules).length + ' items'}`).join(', ');
99
+ return {
100
+ content: [{ type: 'text', text: `Scope: ${scope}\nLanguage: ${contextLanguage}\nRules Applied: ${rulesSummary}\n\nAsync States: ${QUALITY_RULES.ASYNC_STATES.join(', ')}\n\nState Mgmt:\n${Object.entries(QUALITY_RULES.STATE_MANAGEMENT).map(([k, v]) => `- ${k}: ${v}`).join('\n')}` }]
101
+ };
102
+ }