@rahul05ranjan/dhruv-cli 1.3.0 → 1.5.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/.github/ENTERPRISE.md +275 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +45 -17
- package/.github/ISSUE_TEMPLATE/documentation_issue.md +61 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +61 -9
- package/.github/ISSUE_TEMPLATE/security_vulnerability.md +74 -0
- package/.github/dependabot.yml +42 -2
- package/.github/pull_request_template.md +13 -0
- package/.github/workflows/ci.yml +51 -38
- package/.github/workflows/contribution.yml +41 -34
- package/.github/workflows/dependabot-auto-merge.yml +62 -2
- package/.github/workflows/labeler.yml +1 -0
- package/.github/workflows/release.yml +229 -0
- package/.github/workflows/security.yml +201 -0
- package/.releaserc.json +50 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +13 -0
- package/README.md +145 -40
- package/__tests__/cli-contract.test.ts +104 -0
- package/__tests__/core.test.ts +439 -0
- package/__tests__/diagnostics.test.ts +155 -0
- package/__tests__/file-workflows.test.ts +195 -0
- package/__tests__/interactive.test.ts +119 -0
- package/__tests__/setup.ts +62 -0
- package/__tests__/workflows.test.ts +118 -0
- package/dist/commands/explain.js +13 -44
- package/dist/commands/fix.js +13 -38
- package/dist/commands/generate.d.ts +6 -1
- package/dist/commands/generate.js +60 -55
- package/dist/commands/health.d.ts +4 -0
- package/dist/commands/health.js +419 -0
- package/dist/commands/init.js +56 -42
- package/dist/commands/menu.js +137 -24
- package/dist/commands/metrics.d.ts +5 -0
- package/dist/commands/metrics.js +80 -0
- package/dist/commands/optimize.js +42 -34
- package/dist/commands/review.d.ts +4 -1
- package/dist/commands/review.js +87 -43
- package/dist/commands/security-check.d.ts +4 -1
- package/dist/commands/security-check.js +109 -38
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +83 -0
- package/dist/commands/suggest.js +13 -39
- package/dist/config/config.d.ts +5 -1
- package/dist/config/config.js +53 -8
- package/dist/core/ai.d.ts +88 -2
- package/dist/core/ai.js +231 -30
- package/dist/core/command-catalog.d.ts +10 -0
- package/dist/core/command-catalog.js +27 -0
- package/dist/core/command-runner.d.ts +17 -0
- package/dist/core/command-runner.js +157 -0
- package/dist/core/logger.d.ts +40 -0
- package/dist/core/logger.js +139 -0
- package/dist/core/metrics.d.ts +62 -0
- package/dist/core/metrics.js +284 -0
- package/dist/core/prompts.d.ts +1 -0
- package/dist/core/prompts.js +121 -0
- package/dist/core/security.d.ts +34 -0
- package/dist/core/security.js +197 -0
- package/dist/index.js +84 -22
- package/dist/utils/projectType.d.ts +1 -1
- package/dist/utils/projectType.js +26 -7
- package/dist/utils/ux.d.ts +3 -0
- package/dist/utils/ux.js +15 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +45 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/api/.nojekyll +1 -0
- package/docs/api/assets/hierarchy.js +1 -0
- package/docs/api/assets/highlight.css +71 -0
- package/docs/api/assets/icons.js +18 -0
- package/docs/api/assets/icons.svg +1 -0
- package/docs/api/assets/main.js +60 -0
- package/docs/api/assets/navigation.js +1 -0
- package/docs/api/assets/search.js +1 -0
- package/docs/api/assets/style.css +1633 -0
- package/docs/api/hierarchy.html +1 -0
- package/docs/api/index.html +161 -0
- package/docs/api/media/CONTRIBUTING.md +60 -0
- package/docs/api/media/SECURITY.md +8 -0
- package/docs/api/media/dhruv-cli-preview.svg +42 -0
- package/docs/api/media/publishing-fix.md +34 -0
- package/docs/api/modules.html +1 -0
- package/docs/dhruv-cli-preview.svg +42 -0
- package/docs/index.html +631 -533
- package/docs/publishing-fix.md +34 -0
- package/eslint.config.js +170 -0
- package/jest.config.json +37 -0
- package/lighthouserc.json +22 -0
- package/package.json +62 -8
- package/src/commands/explain.ts +13 -42
- package/src/commands/fix.ts +13 -31
- package/src/commands/generate.ts +68 -48
- package/src/commands/health.ts +485 -0
- package/src/commands/init.ts +57 -42
- package/src/commands/menu.ts +132 -24
- package/src/commands/metrics.ts +100 -0
- package/src/commands/optimize.ts +40 -28
- package/src/commands/review.ts +96 -37
- package/src/commands/security-check.ts +127 -33
- package/src/commands/status.ts +83 -0
- package/src/commands/suggest.ts +13 -32
- package/src/config/config.ts +60 -8
- package/src/core/ai.ts +265 -26
- package/src/core/command-catalog.ts +37 -0
- package/src/core/command-runner.ts +185 -0
- package/src/core/logger.ts +195 -0
- package/src/core/metrics.ts +335 -0
- package/src/core/prompts.ts +128 -0
- package/src/core/security.ts +243 -0
- package/src/index.ts +90 -22
- package/src/utils/projectType.ts +22 -7
- package/src/utils/ux.ts +18 -0
- package/test-suite.sh +147 -0
- package/tsconfig.json +4 -3
- package/typedoc.json +44 -0
- package/types/global.d.ts +13 -0
- package/validate-workflows.sh +270 -0
- package/.eslintignore +0 -1
- package/.eslintrc.cjs +0 -43
- package/.github/workflows/auto-assign.yml +0 -14
- package/src/core/ai.test.js +0 -40
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System-message templates per command type. Pure data: no AI plumbing.
|
|
3
|
+
*/
|
|
4
|
+
const SYSTEM_MESSAGES = {
|
|
5
|
+
explain: `You are a knowledgeable programming instructor and technical expert. Your role is to provide clear, accurate, and practical explanations of technical concepts.
|
|
6
|
+
|
|
7
|
+
Guidelines:
|
|
8
|
+
- Provide structured explanations with clear sections
|
|
9
|
+
- Use practical examples where helpful
|
|
10
|
+
- Focus on concepts that developers need to understand
|
|
11
|
+
- Avoid unnecessary jargon, but use precise technical terminology
|
|
12
|
+
- Keep responses concise but comprehensive
|
|
13
|
+
- Format code examples clearly
|
|
14
|
+
- Provide context for why something matters
|
|
15
|
+
|
|
16
|
+
Output Format:
|
|
17
|
+
- Use plain text with clear structure
|
|
18
|
+
- Use bullet points for lists
|
|
19
|
+
- Use numbered steps for procedures
|
|
20
|
+
- Separate code examples with clear labels
|
|
21
|
+
- Avoid markdown formatting except for code blocks when absolutely necessary`,
|
|
22
|
+
suggest: `You are an expert software architect and senior developer providing actionable recommendations and best practices.
|
|
23
|
+
|
|
24
|
+
Guidelines:
|
|
25
|
+
- Provide specific, actionable suggestions
|
|
26
|
+
- Prioritize recommendations by importance
|
|
27
|
+
- Include practical implementation steps
|
|
28
|
+
- Mention relevant tools, libraries, or patterns
|
|
29
|
+
- Consider performance, maintainability, and scalability
|
|
30
|
+
- Provide concrete examples where helpful
|
|
31
|
+
- Focus on industry best practices
|
|
32
|
+
|
|
33
|
+
Output Format:
|
|
34
|
+
- Use clear numbered or bulleted lists
|
|
35
|
+
- Separate different types of suggestions
|
|
36
|
+
- Provide brief explanations for each recommendation
|
|
37
|
+
- Use plain text formatting for better readability`,
|
|
38
|
+
fix: `You are a debugging expert and problem-solving specialist helping developers resolve technical issues.
|
|
39
|
+
|
|
40
|
+
Guidelines:
|
|
41
|
+
- Analyze the problem systematically
|
|
42
|
+
- Identify the root cause
|
|
43
|
+
- Provide step-by-step solutions
|
|
44
|
+
- Include preventive measures
|
|
45
|
+
- Show corrected code examples
|
|
46
|
+
- Explain why the fix works
|
|
47
|
+
- Suggest testing approaches
|
|
48
|
+
|
|
49
|
+
Output Format:
|
|
50
|
+
- Start with problem analysis
|
|
51
|
+
- Provide clear solution steps
|
|
52
|
+
- Show before/after code examples
|
|
53
|
+
- Use plain text with clear structure
|
|
54
|
+
- Avoid complex markdown formatting`,
|
|
55
|
+
review: `You are a senior code reviewer focused on code quality, best practices, and maintainability.
|
|
56
|
+
|
|
57
|
+
Guidelines:
|
|
58
|
+
- Analyze code structure and patterns
|
|
59
|
+
- Identify potential issues or improvements
|
|
60
|
+
- Comment on performance implications
|
|
61
|
+
- Suggest refactoring opportunities
|
|
62
|
+
- Check for security considerations
|
|
63
|
+
- Evaluate readability and maintainability
|
|
64
|
+
- Provide constructive feedback
|
|
65
|
+
|
|
66
|
+
Output Format:
|
|
67
|
+
- Organize feedback by categories (Structure, Performance, Security, etc.)
|
|
68
|
+
- Use clear, actionable language
|
|
69
|
+
- Provide specific line-by-line suggestions where relevant
|
|
70
|
+
- Use plain text formatting for better CLI readability`,
|
|
71
|
+
optimize: `You are a performance optimization expert specializing in code efficiency and best practices.
|
|
72
|
+
|
|
73
|
+
Guidelines:
|
|
74
|
+
- Focus on measurable performance improvements
|
|
75
|
+
- Consider different types of optimization (runtime, memory, bundle size, etc.)
|
|
76
|
+
- Provide specific, implementable suggestions
|
|
77
|
+
- Explain the impact of each optimization
|
|
78
|
+
- Consider trade-offs between performance and maintainability
|
|
79
|
+
- Suggest profiling and measurement approaches
|
|
80
|
+
|
|
81
|
+
Output Format:
|
|
82
|
+
- Categorize optimizations by type
|
|
83
|
+
- Provide clear before/after examples
|
|
84
|
+
- Include estimated impact where possible
|
|
85
|
+
- Use structured plain text formatting`,
|
|
86
|
+
security: `You are a cybersecurity expert specializing in application security and secure coding practices.
|
|
87
|
+
|
|
88
|
+
Guidelines:
|
|
89
|
+
- Identify potential security vulnerabilities
|
|
90
|
+
- Provide remediation steps
|
|
91
|
+
- Suggest secure coding patterns
|
|
92
|
+
- Consider common attack vectors
|
|
93
|
+
- Recommend security tools and practices
|
|
94
|
+
- Focus on practical security measures
|
|
95
|
+
- Explain security implications
|
|
96
|
+
|
|
97
|
+
Output Format:
|
|
98
|
+
- Categorize findings by severity
|
|
99
|
+
- Provide clear remediation steps
|
|
100
|
+
- Use plain text formatting for better readability
|
|
101
|
+
- Include references to security standards where relevant`,
|
|
102
|
+
generate: `You are a code generation specialist creating high-quality, well-structured code.
|
|
103
|
+
|
|
104
|
+
Guidelines:
|
|
105
|
+
- Generate clean, readable code
|
|
106
|
+
- Follow established conventions
|
|
107
|
+
- Include appropriate comments
|
|
108
|
+
- Consider edge cases
|
|
109
|
+
- Use proper error handling
|
|
110
|
+
- Follow best practices for the target language
|
|
111
|
+
- Generate comprehensive test cases when requested
|
|
112
|
+
|
|
113
|
+
Output Format:
|
|
114
|
+
- Provide clean code without excessive markdown
|
|
115
|
+
- Use minimal formatting for better CLI display
|
|
116
|
+
- Include brief explanations only when necessary
|
|
117
|
+
- Focus on practical, working code`,
|
|
118
|
+
};
|
|
119
|
+
export function getSystemMessage(type) {
|
|
120
|
+
return SYSTEM_MESSAGES[type] ?? SYSTEM_MESSAGES.explain;
|
|
121
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface SecurityConfig {
|
|
2
|
+
enableInputValidation: boolean;
|
|
3
|
+
enableRateLimiting: boolean;
|
|
4
|
+
maxInputLength: number;
|
|
5
|
+
allowedCommands: string[];
|
|
6
|
+
blockedPatterns: RegExp[];
|
|
7
|
+
maxRequestsPerMinute: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class SecurityManager {
|
|
10
|
+
private static instance;
|
|
11
|
+
private config;
|
|
12
|
+
private requestCounts;
|
|
13
|
+
private schemas;
|
|
14
|
+
private constructor();
|
|
15
|
+
private initializeSchemas;
|
|
16
|
+
static getInstance(): SecurityManager;
|
|
17
|
+
validateInput(command: string, input: any): {
|
|
18
|
+
valid: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
};
|
|
21
|
+
checkRateLimit(identifier: string): {
|
|
22
|
+
allowed: boolean;
|
|
23
|
+
remainingRequests: number;
|
|
24
|
+
};
|
|
25
|
+
sanitizeInput(input: any): any;
|
|
26
|
+
auditLog(action: string, details: Record<string, any>): void;
|
|
27
|
+
getSecurityStatus(): {
|
|
28
|
+
config: SecurityConfig;
|
|
29
|
+
activeRateLimits: number;
|
|
30
|
+
blockedPatternsCount: number;
|
|
31
|
+
};
|
|
32
|
+
resetRateLimits(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare const securityManager: SecurityManager;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import validator from 'validator';
|
|
3
|
+
import { logger, logSecurity } from './logger.js';
|
|
4
|
+
import { metricsCollector } from './metrics.js';
|
|
5
|
+
export class SecurityManager {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.requestCounts = new Map();
|
|
8
|
+
this.schemas = {};
|
|
9
|
+
this.config = {
|
|
10
|
+
enableInputValidation: process.env.DHRUV_SECURITY_VALIDATION !== 'false',
|
|
11
|
+
enableRateLimiting: process.env.DHRUV_SECURITY_RATE_LIMIT !== 'false',
|
|
12
|
+
maxInputLength: parseInt(process.env.DHRUV_MAX_INPUT_LENGTH || '10000'),
|
|
13
|
+
allowedCommands: [
|
|
14
|
+
'explain', 'suggest', 'fix', 'review', 'optimize',
|
|
15
|
+
'security-check', 'generate', 'init', 'status',
|
|
16
|
+
'project-type', 'menu', 'completion', 'help'
|
|
17
|
+
],
|
|
18
|
+
blockedPatterns: [
|
|
19
|
+
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // XSS patterns
|
|
20
|
+
/\b(eval|exec|system|shell_exec|passthru|proc_open|popen)\b/i, // Code execution
|
|
21
|
+
/\b(rm\s+(-rf|--force)\s+|del\s+)/i, // File deletion
|
|
22
|
+
/\b(format|fdisk|mkfs)\b/i, // Disk operations
|
|
23
|
+
/password\s*=\s*['"][^'"]*['"]/i, // Password exposure
|
|
24
|
+
/\b(ssh|scp|rsync)\s+.*@\w+/i, // SSH operations
|
|
25
|
+
],
|
|
26
|
+
maxRequestsPerMinute: parseInt(process.env.DHRUV_RATE_LIMIT || '60')
|
|
27
|
+
};
|
|
28
|
+
this.initializeSchemas();
|
|
29
|
+
logger.info('Security manager initialized', {
|
|
30
|
+
config: {
|
|
31
|
+
...this.config,
|
|
32
|
+
blockedPatterns: this.config.blockedPatterns.map(p => p.toString())
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
initializeSchemas() {
|
|
37
|
+
this.schemas = {
|
|
38
|
+
explain: Joi.object({
|
|
39
|
+
query: Joi.string()
|
|
40
|
+
.min(1)
|
|
41
|
+
.max(this.config.maxInputLength)
|
|
42
|
+
.required()
|
|
43
|
+
.messages({
|
|
44
|
+
'string.empty': 'Query cannot be empty',
|
|
45
|
+
'string.max': `Query too long (max ${this.config.maxInputLength} characters)`
|
|
46
|
+
})
|
|
47
|
+
}),
|
|
48
|
+
suggest: Joi.object({
|
|
49
|
+
query: Joi.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.max(this.config.maxInputLength)
|
|
52
|
+
.required()
|
|
53
|
+
}),
|
|
54
|
+
fix: Joi.object({
|
|
55
|
+
query: Joi.string()
|
|
56
|
+
.min(1)
|
|
57
|
+
.max(this.config.maxInputLength)
|
|
58
|
+
.required()
|
|
59
|
+
}),
|
|
60
|
+
review: Joi.object({
|
|
61
|
+
fileOrDir: Joi.string()
|
|
62
|
+
.min(1)
|
|
63
|
+
.max(500)
|
|
64
|
+
.pattern(/^[^<>&|;$`]*$/) // Prevent shell injection
|
|
65
|
+
.required()
|
|
66
|
+
}),
|
|
67
|
+
optimize: Joi.object({
|
|
68
|
+
file: Joi.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.max(500)
|
|
71
|
+
.pattern(/^[^<>&|;$`]*$/)
|
|
72
|
+
.required()
|
|
73
|
+
}),
|
|
74
|
+
generate: Joi.object({
|
|
75
|
+
type: Joi.string()
|
|
76
|
+
.valid('tests', 'documentation', 'docs', 'component')
|
|
77
|
+
.required(),
|
|
78
|
+
target: Joi.string()
|
|
79
|
+
.min(1)
|
|
80
|
+
.max(500)
|
|
81
|
+
.pattern(/^[^<>&|;$`]*$/)
|
|
82
|
+
.required()
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
static getInstance() {
|
|
87
|
+
if (!SecurityManager.instance) {
|
|
88
|
+
SecurityManager.instance = new SecurityManager();
|
|
89
|
+
}
|
|
90
|
+
return SecurityManager.instance;
|
|
91
|
+
}
|
|
92
|
+
validateInput(command, input) {
|
|
93
|
+
if (!this.config.enableInputValidation) {
|
|
94
|
+
return { valid: true };
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
// Check command is allowed
|
|
98
|
+
if (!this.config.allowedCommands.includes(command)) {
|
|
99
|
+
const error = `Command '${command}' is not allowed`;
|
|
100
|
+
logSecurity('blocked_command', { command, input });
|
|
101
|
+
metricsCollector.recordError('blocked_command', command);
|
|
102
|
+
return { valid: false, error };
|
|
103
|
+
}
|
|
104
|
+
// Get schema for command
|
|
105
|
+
const schema = this.schemas[command];
|
|
106
|
+
if (!schema) {
|
|
107
|
+
return { valid: true }; // No specific validation for this command
|
|
108
|
+
}
|
|
109
|
+
// Validate input
|
|
110
|
+
const { error } = schema.validate(input, { abortEarly: false });
|
|
111
|
+
if (error) {
|
|
112
|
+
const errorMessage = error.details.map((d) => d.message).join(', ');
|
|
113
|
+
logSecurity('invalid_input', { command, input, error: errorMessage });
|
|
114
|
+
metricsCollector.recordError('invalid_input', command);
|
|
115
|
+
return { valid: false, error: errorMessage };
|
|
116
|
+
}
|
|
117
|
+
// Check for blocked patterns
|
|
118
|
+
const inputString = JSON.stringify(input);
|
|
119
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
120
|
+
if (pattern.test(inputString)) {
|
|
121
|
+
const error = 'Input contains blocked patterns';
|
|
122
|
+
logSecurity('blocked_pattern', { command, input, pattern: pattern.toString() });
|
|
123
|
+
metricsCollector.recordError('blocked_pattern', command);
|
|
124
|
+
return { valid: false, error };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Sanitize input
|
|
128
|
+
const sanitized = this.sanitizeInput(input);
|
|
129
|
+
if (JSON.stringify(sanitized) !== JSON.stringify(input)) {
|
|
130
|
+
logger.warn('Input was sanitized', { command, original: input, sanitized });
|
|
131
|
+
}
|
|
132
|
+
return { valid: true };
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
const error = `Validation error: ${err.message}`;
|
|
136
|
+
logSecurity('validation_error', { command, input, error });
|
|
137
|
+
metricsCollector.recordError('validation_error', command);
|
|
138
|
+
return { valid: false, error };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
checkRateLimit(identifier) {
|
|
142
|
+
if (!this.config.enableRateLimiting) {
|
|
143
|
+
return { allowed: true, remainingRequests: this.config.maxRequestsPerMinute };
|
|
144
|
+
}
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
const windowStart = Math.floor(now / 60000) * 60000; // 1-minute windows
|
|
147
|
+
const windowKey = `${identifier}:${windowStart}`;
|
|
148
|
+
const current = this.requestCounts.get(windowKey) || { count: 0, resetTime: windowStart + 60000 };
|
|
149
|
+
if (now > current.resetTime) {
|
|
150
|
+
// Reset window
|
|
151
|
+
this.requestCounts.set(windowKey, { count: 1, resetTime: windowStart + 60000 });
|
|
152
|
+
return { allowed: true, remainingRequests: this.config.maxRequestsPerMinute - 1 };
|
|
153
|
+
}
|
|
154
|
+
if (current.count >= this.config.maxRequestsPerMinute) {
|
|
155
|
+
logSecurity('rate_limit_exceeded', { identifier, count: current.count });
|
|
156
|
+
metricsCollector.recordError('rate_limit_exceeded', 'unknown');
|
|
157
|
+
return { allowed: false, remainingRequests: 0 };
|
|
158
|
+
}
|
|
159
|
+
current.count++;
|
|
160
|
+
const remaining = Math.max(0, this.config.maxRequestsPerMinute - current.count);
|
|
161
|
+
return { allowed: true, remainingRequests: remaining };
|
|
162
|
+
}
|
|
163
|
+
sanitizeInput(input) {
|
|
164
|
+
if (typeof input === 'string') {
|
|
165
|
+
return validator.escape(input);
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(input)) {
|
|
168
|
+
return input.map(item => this.sanitizeInput(item));
|
|
169
|
+
}
|
|
170
|
+
if (typeof input === 'object' && input !== null) {
|
|
171
|
+
const sanitized = {};
|
|
172
|
+
for (const [key, value] of Object.entries(input)) {
|
|
173
|
+
sanitized[key] = this.sanitizeInput(value);
|
|
174
|
+
}
|
|
175
|
+
return sanitized;
|
|
176
|
+
}
|
|
177
|
+
return input;
|
|
178
|
+
}
|
|
179
|
+
auditLog(action, details) {
|
|
180
|
+
logSecurity(action, {
|
|
181
|
+
timestamp: new Date().toISOString(),
|
|
182
|
+
...details
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
getSecurityStatus() {
|
|
186
|
+
return {
|
|
187
|
+
config: this.config,
|
|
188
|
+
activeRateLimits: this.requestCounts.size,
|
|
189
|
+
blockedPatternsCount: this.config.blockedPatterns.length
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
resetRateLimits() {
|
|
193
|
+
this.requestCounts.clear();
|
|
194
|
+
logger.info('Rate limits reset');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export const securityManager = SecurityManager.getInstance();
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,17 @@ import { optimize } from './commands/optimize.js';
|
|
|
11
11
|
import { securityCheck } from './commands/security-check.js';
|
|
12
12
|
import { generate } from './commands/generate.js';
|
|
13
13
|
import { init } from './commands/init.js';
|
|
14
|
+
import { status } from './commands/status.js';
|
|
15
|
+
import { health } from './commands/health.js';
|
|
16
|
+
import { metrics } from './commands/metrics.js';
|
|
14
17
|
import { detectProjectType } from './utils/projectType.js';
|
|
15
18
|
import { menu } from './commands/menu.js';
|
|
16
|
-
import
|
|
19
|
+
import { createRequire } from 'module';
|
|
20
|
+
import { logger, logInfo, logError } from './core/logger.js';
|
|
21
|
+
import { metricsCollector } from './core/metrics.js';
|
|
22
|
+
import { commandDescription, completionCommands, completionOptions } from './core/command-catalog.js';
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
const pkg = require('../package.json');
|
|
17
25
|
const program = new Command();
|
|
18
26
|
program
|
|
19
27
|
.name('dhruv')
|
|
@@ -21,54 +29,78 @@ program
|
|
|
21
29
|
.version(pkg.version);
|
|
22
30
|
program
|
|
23
31
|
.command('explain <query>')
|
|
24
|
-
.description('
|
|
32
|
+
.description(commandDescription('explain'))
|
|
33
|
+
.addHelpText('after', '\nExamples:\n $ dhruv explain "What is async/await?"\n $ dhruv explain "Docker containers vs VMs"')
|
|
25
34
|
.action(explain);
|
|
26
35
|
program
|
|
27
36
|
.command('suggest <query>')
|
|
28
|
-
.description('
|
|
37
|
+
.description(commandDescription('suggest'))
|
|
38
|
+
.addHelpText('after', '\nExamples:\n $ dhruv suggest "React performance optimization"\n $ dhruv suggest "Node.js project structure"')
|
|
29
39
|
.action(suggest);
|
|
30
40
|
program
|
|
31
41
|
.command('fix <query>')
|
|
32
|
-
.description('
|
|
42
|
+
.description(commandDescription('fix'))
|
|
43
|
+
.addHelpText('after', '\nExamples:\n $ dhruv fix "TypeError: Cannot read property of undefined"\n $ dhruv fix "CORS error in Express.js"')
|
|
33
44
|
.action(fix);
|
|
34
45
|
program
|
|
35
46
|
.command('review <fileOrDir>')
|
|
36
|
-
.description('
|
|
37
|
-
.
|
|
47
|
+
.description(commandDescription('review'))
|
|
48
|
+
.option('--diff', 'Review the current uncommitted git diff')
|
|
49
|
+
.action((fileOrDir, command) => review(fileOrDir, command.opts()));
|
|
38
50
|
program
|
|
39
51
|
.command('optimize <file>')
|
|
40
|
-
.description(
|
|
52
|
+
.description(commandDescription('optimize'))
|
|
41
53
|
.action(optimize);
|
|
42
54
|
program
|
|
43
55
|
.command('security-check [fileOrDir]')
|
|
44
|
-
.description('
|
|
45
|
-
.
|
|
56
|
+
.description(commandDescription('security-check'))
|
|
57
|
+
.option('--strict', 'Exit with failure when high-confidence findings are detected')
|
|
58
|
+
.action((fileOrDir, command) => securityCheck(fileOrDir, command.opts()));
|
|
46
59
|
program
|
|
47
60
|
.command('generate <type> <target>')
|
|
48
|
-
.description('
|
|
49
|
-
.
|
|
61
|
+
.description(commandDescription('generate'))
|
|
62
|
+
.option('--apply', 'Write generated tests to disk (preview is the default)')
|
|
63
|
+
.option('--output <path>', 'Write generated tests to this path')
|
|
64
|
+
.option('--overwrite', 'Allow replacing an existing output file')
|
|
65
|
+
.action((type, target, command) => generate(type, target, command.opts()));
|
|
50
66
|
program
|
|
51
67
|
.command('init')
|
|
52
|
-
.description('
|
|
68
|
+
.description(commandDescription('init'))
|
|
53
69
|
.action(init);
|
|
70
|
+
program
|
|
71
|
+
.command('status')
|
|
72
|
+
.description(commandDescription('status'))
|
|
73
|
+
.action(status);
|
|
74
|
+
program
|
|
75
|
+
.command('health')
|
|
76
|
+
.description(commandDescription('health'))
|
|
77
|
+
.option('--details', 'Show every health check and diagnostic detail')
|
|
78
|
+
.action((command) => health(command.opts()));
|
|
79
|
+
program
|
|
80
|
+
.command('metrics')
|
|
81
|
+
.description(commandDescription('metrics'))
|
|
82
|
+
.option('--raw', 'Export raw Prometheus metrics')
|
|
83
|
+
.option('--reset', 'Clear persisted local metrics')
|
|
84
|
+
.action((command) => metrics(command.opts()));
|
|
54
85
|
program
|
|
55
86
|
.command('project-type')
|
|
56
|
-
.description('
|
|
87
|
+
.description(commandDescription('project-type'))
|
|
57
88
|
.action(() => {
|
|
58
89
|
const type = detectProjectType();
|
|
59
90
|
console.log(chalk.blue(`Detected project type: ${type}`));
|
|
60
91
|
});
|
|
61
92
|
program
|
|
62
93
|
.command('menu')
|
|
63
|
-
.description('
|
|
94
|
+
.description(commandDescription('menu'))
|
|
64
95
|
.action(menu);
|
|
65
96
|
program
|
|
66
97
|
.option('--model <model>', 'Set Ollama model')
|
|
67
98
|
.option('--verbose', 'Enable verbose output')
|
|
68
99
|
.option('--json', 'Output in JSON format')
|
|
100
|
+
.option('--timeout <milliseconds>', 'Set the AI request timeout')
|
|
69
101
|
.hook('preAction', async (thisCommand) => {
|
|
70
102
|
const opts = thisCommand.opts();
|
|
71
|
-
if (opts.model || opts.verbose || opts.json) {
|
|
103
|
+
if (opts.model || opts.verbose || opts.json || opts.timeout) {
|
|
72
104
|
const config = {};
|
|
73
105
|
if (opts.model)
|
|
74
106
|
config.model = opts.model;
|
|
@@ -76,6 +108,8 @@ program
|
|
|
76
108
|
config.verbose = true;
|
|
77
109
|
if (opts.json)
|
|
78
110
|
config.responseFormat = 'json';
|
|
111
|
+
if (opts.timeout)
|
|
112
|
+
config.timeoutMs = Number(opts.timeout);
|
|
79
113
|
// Save config for session
|
|
80
114
|
const configModule = await import('./config/config.js');
|
|
81
115
|
configModule.saveConfig(config);
|
|
@@ -102,29 +136,57 @@ async function loadPlugins(program) {
|
|
|
102
136
|
}
|
|
103
137
|
}
|
|
104
138
|
(async () => {
|
|
105
|
-
|
|
106
|
-
|
|
139
|
+
// Initialize enterprise features
|
|
140
|
+
try {
|
|
141
|
+
logInfo('Dhruv CLI starting', {
|
|
142
|
+
version: pkg.version,
|
|
143
|
+
nodeVersion: process.version,
|
|
144
|
+
platform: process.platform
|
|
145
|
+
});
|
|
146
|
+
// Record session start
|
|
147
|
+
metricsCollector.recordSession();
|
|
148
|
+
await loadPlugins(program);
|
|
149
|
+
program.parse(process.argv);
|
|
150
|
+
// Record successful session
|
|
151
|
+
const sessionId = logger.getSessionId();
|
|
152
|
+
logInfo('CLI session completed', { sessionId });
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logError('CLI startup failed', error);
|
|
156
|
+
console.error(chalk.red('Failed to start Dhruv CLI:'), error.message);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
107
159
|
})();
|
|
108
160
|
// Autocomplete: Generate shell completion scripts
|
|
109
161
|
program
|
|
110
162
|
.command('completion')
|
|
111
|
-
.description('
|
|
163
|
+
.description(commandDescription('completion'))
|
|
112
164
|
.argument('[shell]', 'shell type (bash|zsh|fish)', 'bash')
|
|
113
165
|
.action((shell) => {
|
|
166
|
+
const commands = completionCommands();
|
|
167
|
+
const options = completionOptions();
|
|
114
168
|
let script = '';
|
|
115
169
|
switch (shell) {
|
|
116
170
|
case 'zsh':
|
|
117
|
-
script = `#compdef dhruv\n_dhruv_completion() {\n
|
|
171
|
+
script = `#compdef dhruv\n_dhruv_completion() {\n _arguments '1:command:(${commands})' '*:option:(${options})'\n}\ncompdef _dhruv_completion dhruv`;
|
|
118
172
|
break;
|
|
119
173
|
case 'fish':
|
|
120
|
-
script = `
|
|
174
|
+
script = `complete -c dhruv -f -n '__fish_use_subcommand' -a '${commands}'\ncomplete -c dhruv -f -n 'not __fish_use_subcommand' -a '${options}'`;
|
|
121
175
|
break;
|
|
122
|
-
|
|
176
|
+
case 'bash':
|
|
123
177
|
script = String.raw `#!/bin/bash
|
|
124
178
|
_dhruv_completion() {
|
|
125
|
-
|
|
179
|
+
local commands="${commands}"
|
|
180
|
+
local options="${options}"
|
|
181
|
+
local choices="$commands $options"
|
|
182
|
+
COMPREPLY=( $(compgen -W "$choices" -- "\${COMP_WORDS[COMP_CWORD]}") )
|
|
126
183
|
}
|
|
127
184
|
complete -F _dhruv_completion dhruv`;
|
|
185
|
+
break;
|
|
186
|
+
default:
|
|
187
|
+
console.error(chalk.red(`Unsupported shell "${shell}". Choose bash, zsh, or fish.`));
|
|
188
|
+
process.exitCode = 2;
|
|
189
|
+
return;
|
|
128
190
|
}
|
|
129
191
|
console.log(script);
|
|
130
192
|
console.log(`\n# To enable tab completion, add the above to your shell profile or source it directly.`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function detectProjectType(): string;
|
|
1
|
+
export declare function detectProjectType(directory?: string): string;
|
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
// Use .js extension for ESM compatibility
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import path from 'path';
|
|
4
|
+
export function detectProjectType(directory = process.cwd()) {
|
|
5
|
+
const file = (name) => path.join(directory, name);
|
|
6
|
+
if (fs.existsSync(file('package.json'))) {
|
|
7
|
+
let pkg;
|
|
8
|
+
try {
|
|
9
|
+
pkg = JSON.parse(fs.readFileSync(file('package.json'), 'utf-8'));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return 'unknown';
|
|
13
|
+
}
|
|
14
|
+
const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
15
|
+
if (dependencies.react)
|
|
7
16
|
return 'react';
|
|
8
|
-
if (
|
|
17
|
+
if (dependencies.next)
|
|
18
|
+
return 'nextjs';
|
|
19
|
+
if (dependencies.express)
|
|
9
20
|
return 'node-express';
|
|
21
|
+
if (dependencies.typescript || fs.existsSync(file('tsconfig.json')))
|
|
22
|
+
return 'node-typescript';
|
|
10
23
|
return 'node';
|
|
11
24
|
}
|
|
12
|
-
if (fs.existsSync('requirements.txt'))
|
|
25
|
+
if (fs.existsSync(file('requirements.txt')))
|
|
13
26
|
return 'python';
|
|
14
|
-
if (fs.existsSync('pyproject.toml'))
|
|
27
|
+
if (fs.existsSync(file('pyproject.toml')))
|
|
15
28
|
return 'python';
|
|
29
|
+
if (fs.existsSync(file('go.mod')))
|
|
30
|
+
return 'go';
|
|
31
|
+
if (fs.existsSync(file('Cargo.toml')))
|
|
32
|
+
return 'rust';
|
|
33
|
+
if (fs.existsSync(file('pom.xml')) || fs.existsSync(file('build.gradle')))
|
|
34
|
+
return 'java';
|
|
16
35
|
return 'unknown';
|
|
17
36
|
}
|
package/dist/utils/ux.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export declare function highlightCode(code: string, lang?: string): string;
|
|
2
2
|
export declare function printError(message: string): void;
|
|
3
3
|
export declare function printSuccess(message: string): void;
|
|
4
|
+
export declare function printWarning(message: string): void;
|
|
5
|
+
export declare function printInfo(message: string): void;
|
|
6
|
+
export declare function createSpinner(text: string): import("ora").Ora;
|
|
4
7
|
export declare function createProgressBar(total: number): any;
|
|
5
8
|
export declare function themed(text: string, type?: 'primary' | 'accent'): string;
|
package/dist/utils/ux.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
// @ts-expect-error: cli-progress has no type definitions
|
|
3
3
|
import cliProgress from 'cli-progress';
|
|
4
4
|
import { highlight } from 'cli-highlight';
|
|
5
|
+
import ora from 'ora';
|
|
5
6
|
import { loadConfig } from '../config/config.js';
|
|
6
7
|
function getTheme() {
|
|
7
8
|
const config = loadConfig();
|
|
@@ -27,6 +28,20 @@ export function printSuccess(message) {
|
|
|
27
28
|
const { success } = getTheme();
|
|
28
29
|
console.log(success(' SUCCESS '), success(message));
|
|
29
30
|
}
|
|
31
|
+
export function printWarning(message) {
|
|
32
|
+
const { accent } = getTheme();
|
|
33
|
+
console.warn(chalk.bgYellow.black(' WARNING '), accent(message));
|
|
34
|
+
}
|
|
35
|
+
export function printInfo(message) {
|
|
36
|
+
const { primary } = getTheme();
|
|
37
|
+
console.log(chalk.bgBlue.white(' INFO '), primary(message));
|
|
38
|
+
}
|
|
39
|
+
export function createSpinner(text) {
|
|
40
|
+
return ora({
|
|
41
|
+
text: themed(text, 'primary'),
|
|
42
|
+
color: 'cyan',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
30
45
|
export function createProgressBar(total) {
|
|
31
46
|
const { primary } = getTheme();
|
|
32
47
|
const bar = new cliProgress.SingleBar({
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Domain Docs
|
|
2
|
+
|
|
3
|
+
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
|
4
|
+
|
|
5
|
+
## Before exploring, read these
|
|
6
|
+
|
|
7
|
+
- **`CONTEXT.md`** at the repo root, or
|
|
8
|
+
- **`CONTEXT-MAP.md`** at the repo root if it exists: it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
|
9
|
+
- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
|
10
|
+
|
|
11
|
+
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
|
12
|
+
|
|
13
|
+
## File structure
|
|
14
|
+
|
|
15
|
+
Single-context repo (most repos):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/
|
|
19
|
+
├── CONTEXT.md
|
|
20
|
+
├── docs/adr/
|
|
21
|
+
│ ├── 0001-event-sourced-orders.md
|
|
22
|
+
│ └── 0002-postgres-for-write-model.md
|
|
23
|
+
└── src/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
/
|
|
30
|
+
├── CONTEXT-MAP.md
|
|
31
|
+
├── docs/adr/ ← system-wide decisions
|
|
32
|
+
└── src/
|
|
33
|
+
├── ordering/
|
|
34
|
+
│ ├── CONTEXT.md
|
|
35
|
+
│ └── docs/adr/ ← context-specific decisions
|
|
36
|
+
└── billing/
|
|
37
|
+
├── CONTEXT.md
|
|
38
|
+
└── docs/adr/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use the glossary's vocabulary
|
|
42
|
+
|
|
43
|
+
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
|
44
|
+
|
|
45
|
+
If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
|
46
|
+
|
|
47
|
+
## Flag ADR conflicts
|
|
48
|
+
|
|
49
|
+
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
|
50
|
+
|
|
51
|
+
> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_
|