@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.
Files changed (121) hide show
  1. package/.github/ENTERPRISE.md +275 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +45 -17
  3. package/.github/ISSUE_TEMPLATE/documentation_issue.md +61 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +61 -9
  5. package/.github/ISSUE_TEMPLATE/security_vulnerability.md +74 -0
  6. package/.github/dependabot.yml +42 -2
  7. package/.github/pull_request_template.md +13 -0
  8. package/.github/workflows/ci.yml +51 -38
  9. package/.github/workflows/contribution.yml +41 -34
  10. package/.github/workflows/dependabot-auto-merge.yml +62 -2
  11. package/.github/workflows/labeler.yml +1 -0
  12. package/.github/workflows/release.yml +229 -0
  13. package/.github/workflows/security.yml +201 -0
  14. package/.releaserc.json +50 -0
  15. package/AGENTS.md +13 -0
  16. package/CHANGELOG.md +13 -0
  17. package/README.md +145 -40
  18. package/__tests__/cli-contract.test.ts +104 -0
  19. package/__tests__/core.test.ts +439 -0
  20. package/__tests__/diagnostics.test.ts +155 -0
  21. package/__tests__/file-workflows.test.ts +195 -0
  22. package/__tests__/interactive.test.ts +119 -0
  23. package/__tests__/setup.ts +62 -0
  24. package/__tests__/workflows.test.ts +118 -0
  25. package/dist/commands/explain.js +13 -44
  26. package/dist/commands/fix.js +13 -38
  27. package/dist/commands/generate.d.ts +6 -1
  28. package/dist/commands/generate.js +60 -55
  29. package/dist/commands/health.d.ts +4 -0
  30. package/dist/commands/health.js +419 -0
  31. package/dist/commands/init.js +56 -42
  32. package/dist/commands/menu.js +137 -24
  33. package/dist/commands/metrics.d.ts +5 -0
  34. package/dist/commands/metrics.js +80 -0
  35. package/dist/commands/optimize.js +42 -34
  36. package/dist/commands/review.d.ts +4 -1
  37. package/dist/commands/review.js +87 -43
  38. package/dist/commands/security-check.d.ts +4 -1
  39. package/dist/commands/security-check.js +109 -38
  40. package/dist/commands/status.d.ts +1 -0
  41. package/dist/commands/status.js +83 -0
  42. package/dist/commands/suggest.js +13 -39
  43. package/dist/config/config.d.ts +5 -1
  44. package/dist/config/config.js +53 -8
  45. package/dist/core/ai.d.ts +88 -2
  46. package/dist/core/ai.js +231 -30
  47. package/dist/core/command-catalog.d.ts +10 -0
  48. package/dist/core/command-catalog.js +27 -0
  49. package/dist/core/command-runner.d.ts +17 -0
  50. package/dist/core/command-runner.js +157 -0
  51. package/dist/core/logger.d.ts +40 -0
  52. package/dist/core/logger.js +139 -0
  53. package/dist/core/metrics.d.ts +62 -0
  54. package/dist/core/metrics.js +284 -0
  55. package/dist/core/prompts.d.ts +1 -0
  56. package/dist/core/prompts.js +121 -0
  57. package/dist/core/security.d.ts +34 -0
  58. package/dist/core/security.js +197 -0
  59. package/dist/index.js +84 -22
  60. package/dist/utils/projectType.d.ts +1 -1
  61. package/dist/utils/projectType.js +26 -7
  62. package/dist/utils/ux.d.ts +3 -0
  63. package/dist/utils/ux.js +15 -0
  64. package/docs/agents/domain.md +51 -0
  65. package/docs/agents/issue-tracker.md +45 -0
  66. package/docs/agents/triage-labels.md +15 -0
  67. package/docs/api/.nojekyll +1 -0
  68. package/docs/api/assets/hierarchy.js +1 -0
  69. package/docs/api/assets/highlight.css +71 -0
  70. package/docs/api/assets/icons.js +18 -0
  71. package/docs/api/assets/icons.svg +1 -0
  72. package/docs/api/assets/main.js +60 -0
  73. package/docs/api/assets/navigation.js +1 -0
  74. package/docs/api/assets/search.js +1 -0
  75. package/docs/api/assets/style.css +1633 -0
  76. package/docs/api/hierarchy.html +1 -0
  77. package/docs/api/index.html +161 -0
  78. package/docs/api/media/CONTRIBUTING.md +60 -0
  79. package/docs/api/media/SECURITY.md +8 -0
  80. package/docs/api/media/dhruv-cli-preview.svg +42 -0
  81. package/docs/api/media/publishing-fix.md +34 -0
  82. package/docs/api/modules.html +1 -0
  83. package/docs/dhruv-cli-preview.svg +42 -0
  84. package/docs/index.html +631 -533
  85. package/docs/publishing-fix.md +34 -0
  86. package/eslint.config.js +170 -0
  87. package/jest.config.json +37 -0
  88. package/lighthouserc.json +22 -0
  89. package/package.json +62 -8
  90. package/src/commands/explain.ts +13 -42
  91. package/src/commands/fix.ts +13 -31
  92. package/src/commands/generate.ts +68 -48
  93. package/src/commands/health.ts +485 -0
  94. package/src/commands/init.ts +57 -42
  95. package/src/commands/menu.ts +132 -24
  96. package/src/commands/metrics.ts +100 -0
  97. package/src/commands/optimize.ts +40 -28
  98. package/src/commands/review.ts +96 -37
  99. package/src/commands/security-check.ts +127 -33
  100. package/src/commands/status.ts +83 -0
  101. package/src/commands/suggest.ts +13 -32
  102. package/src/config/config.ts +60 -8
  103. package/src/core/ai.ts +265 -26
  104. package/src/core/command-catalog.ts +37 -0
  105. package/src/core/command-runner.ts +185 -0
  106. package/src/core/logger.ts +195 -0
  107. package/src/core/metrics.ts +335 -0
  108. package/src/core/prompts.ts +128 -0
  109. package/src/core/security.ts +243 -0
  110. package/src/index.ts +90 -22
  111. package/src/utils/projectType.ts +22 -7
  112. package/src/utils/ux.ts +18 -0
  113. package/test-suite.sh +147 -0
  114. package/tsconfig.json +4 -3
  115. package/typedoc.json +44 -0
  116. package/types/global.d.ts +13 -0
  117. package/validate-workflows.sh +270 -0
  118. package/.eslintignore +0 -1
  119. package/.eslintrc.cjs +0 -43
  120. package/.github/workflows/auto-assign.yml +0 -14
  121. package/src/core/ai.test.js +0 -40
@@ -0,0 +1,34 @@
1
+ # npm publishing issue resolution
2
+
3
+ ## Issue
4
+
5
+ GitHub Actions was failing with:
6
+
7
+ ```text
8
+ npm ERR! 403 403 Forbidden - PUT https://registry.npmjs.org/@rahul05ranjan%2fdhruv-cli - You cannot publish over the previously published versions: 0.0.0-development.
9
+ ```
10
+
11
+ ## Resolution
12
+
13
+ - Updated the package version from `0.0.0-development` to `1.4.0`.
14
+ - Added semantic-release configuration and plugins.
15
+ - Removed direct npm publishing from the CI workflow.
16
+ - Added a dedicated publishing workflow with version-conflict checks.
17
+ - Enabled trusted publishing with npm provenance.
18
+
19
+ ## Release conventions
20
+
21
+ Use conventional commit messages so semantic-release can determine the next version:
22
+
23
+ ```bash
24
+ # Patch
25
+ git commit -m "fix: resolve a connection issue"
26
+
27
+ # Minor
28
+ git commit -m "feat: add a new command"
29
+
30
+ # Major
31
+ git commit -m "feat!: change the command output contract"
32
+ ```
33
+
34
+ The authoritative release configuration lives in [`.releaserc.json`](../.releaserc.json) and the workflows live in [`.github/workflows/`](../.github/workflows/).
@@ -0,0 +1,170 @@
1
+ import js from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+
4
+ export default [
5
+ // Base configuration
6
+ js.configs.recommended,
7
+
8
+ // Global ignores (migrated from .eslintignore)
9
+ {
10
+ ignores: [
11
+ 'dist/',
12
+ 'node_modules/',
13
+ 'node-fetch.d.ts',
14
+ 'coverage/',
15
+ '.dhruv-cache/',
16
+ 'logs/',
17
+ 'docs/'
18
+ ]
19
+ },
20
+
21
+ // TypeScript files configuration (excluding test files)
22
+ {
23
+ files: ['**/*.ts', '**/*.tsx'],
24
+ ignores: ['__tests__/**/*', '**/*.test.ts', '**/*.spec.ts', 'src/core/logger.ts', 'src/core/security.ts'],
25
+ languageOptions: {
26
+ parser: tseslint.parser,
27
+ parserOptions: {
28
+ project: './tsconfig.json',
29
+ sourceType: 'module',
30
+ ecmaVersion: 2020,
31
+ },
32
+ globals: {
33
+ console: 'readonly',
34
+ process: 'readonly',
35
+ Buffer: 'readonly',
36
+ setTimeout: 'readonly',
37
+ __dirname: 'readonly',
38
+ __filename: 'readonly',
39
+ global: 'readonly',
40
+ module: 'readonly',
41
+ require: 'readonly',
42
+ exports: 'readonly',
43
+ },
44
+ },
45
+ plugins: {
46
+ '@typescript-eslint': tseslint.plugin,
47
+ },
48
+ rules: {
49
+ ...tseslint.configs.recommended.rules,
50
+ 'no-console': 'off',
51
+ '@typescript-eslint/no-unused-vars': ['warn', {
52
+ 'argsIgnorePattern': '^_|error|e',
53
+ 'varsIgnorePattern': '^_|response|error|e|Command|printSuccess|printInfo|printWarning|detectProjectType',
54
+ 'ignoreRestSiblings': true
55
+ }],
56
+ '@typescript-eslint/explicit-function-return-type': 'off',
57
+ '@typescript-eslint/no-explicit-any': ['warn', {
58
+ 'ignoreRestArgs': true
59
+ }],
60
+ 'no-var': 'error',
61
+ 'prefer-const': 'error',
62
+ 'eqeqeq': ['error', 'always'],
63
+ 'no-undef': 'off', // Turn off because we're using globals
64
+ 'no-redeclare': 'off', // Allow redeclaration of globals
65
+ 'no-unused-vars': 'off', // Turn off base rule, use @typescript-eslint/no-unused-vars
66
+ },
67
+ },
68
+
69
+ // Lenient configuration for files with external library interfaces
70
+ {
71
+ files: ['src/core/logger.ts', 'src/core/security.ts'],
72
+ languageOptions: {
73
+ parser: tseslint.parser,
74
+ parserOptions: {
75
+ project: './tsconfig.json',
76
+ sourceType: 'module',
77
+ ecmaVersion: 2020,
78
+ },
79
+ },
80
+ plugins: {
81
+ '@typescript-eslint': tseslint.plugin,
82
+ },
83
+ rules: {
84
+ ...tseslint.configs.recommended.rules,
85
+ 'no-console': 'off',
86
+ '@typescript-eslint/no-unused-vars': 'off',
87
+ '@typescript-eslint/explicit-function-return-type': 'off',
88
+ '@typescript-eslint/no-explicit-any': 'off', // Allow any in these files due to Winston/security libraries
89
+ 'no-var': 'error',
90
+ 'prefer-const': 'error',
91
+ 'eqeqeq': ['error', 'always'],
92
+ 'no-undef': 'off',
93
+ 'no-redeclare': 'off',
94
+ 'no-unused-vars': 'off',
95
+ },
96
+ },
97
+
98
+ // JavaScript files configuration
99
+ {
100
+ files: ['**/*.js'],
101
+ languageOptions: {
102
+ sourceType: 'module',
103
+ ecmaVersion: 2020,
104
+ globals: {
105
+ console: 'readonly',
106
+ process: 'readonly',
107
+ Buffer: 'readonly',
108
+ setTimeout: 'readonly',
109
+ __dirname: 'readonly',
110
+ __filename: 'readonly',
111
+ global: 'readonly',
112
+ module: 'readonly',
113
+ require: 'readonly',
114
+ exports: 'readonly',
115
+ },
116
+ },
117
+ rules: {
118
+ 'no-console': 'off',
119
+ 'no-var': 'error',
120
+ 'prefer-const': 'error',
121
+ 'eqeqeq': ['error', 'always'],
122
+ 'no-undef': 'off', // Turn off because we're using globals
123
+ 'no-redeclare': 'off', // Allow redeclaration of globals
124
+ 'no-unused-vars': 'off', // Allow unused vars in JS files for flexibility
125
+ },
126
+ },
127
+
128
+ // Test files configuration (separate project config)
129
+ {
130
+ files: ['__tests__/**/*.{ts,js}', '**/*.test.{ts,js}', '**/*.spec.{ts,js}'],
131
+ languageOptions: {
132
+ parser: tseslint.parser,
133
+ parserOptions: {
134
+ sourceType: 'module',
135
+ ecmaVersion: 2020,
136
+ // Don't use project for test files since they're not in tsconfig.json
137
+ },
138
+ globals: {
139
+ console: 'readonly',
140
+ process: 'readonly',
141
+ Buffer: 'readonly',
142
+ setTimeout: 'readonly',
143
+ describe: 'readonly',
144
+ it: 'readonly',
145
+ test: 'readonly',
146
+ expect: 'readonly',
147
+ beforeEach: 'readonly',
148
+ afterEach: 'readonly',
149
+ beforeAll: 'readonly',
150
+ afterAll: 'readonly',
151
+ jest: 'readonly',
152
+ },
153
+ },
154
+ plugins: {
155
+ '@typescript-eslint': tseslint.plugin,
156
+ },
157
+ rules: {
158
+ '@typescript-eslint/no-unused-vars': ['warn', {
159
+ 'argsIgnorePattern': '^_|error|e',
160
+ 'varsIgnorePattern': '^_|response|error|e|Command|printSuccess|printInfo|printWarning|detectProjectType',
161
+ 'ignoreRestSiblings': true
162
+ }],
163
+ '@typescript-eslint/explicit-function-return-type': 'off',
164
+ '@typescript-eslint/no-explicit-any': 'warn',
165
+ 'no-undef': 'off', // Turn off because we're using globals
166
+ 'no-redeclare': 'off', // Allow redeclaration of globals
167
+ 'no-unused-vars': 'off', // Turn off base rule, use @typescript-eslint/no-unused-vars
168
+ },
169
+ },
170
+ ];
@@ -0,0 +1,37 @@
1
+ {
2
+ "preset": "ts-jest",
3
+ "testEnvironment": "node",
4
+ "roots": ["<rootDir>/src", "<rootDir>/__tests__"],
5
+ "testMatch": ["**/__tests__/**/*.test.ts", "**/*.test.ts", "**/*.spec.ts"],
6
+ "testPathIgnorePatterns": ["/node_modules/", "/dist/", "/coverage/", "\\.test\\.js$", "\\.spec\\.js$"],
7
+ "transform": {
8
+ "^.+\\.ts$": "ts-jest"
9
+ },
10
+ "moduleNameMapper": {
11
+ "^(\\.{1,2}/.*)\\.js$": "$1"
12
+ },
13
+ "collectCoverageFrom": [
14
+ "src/**/*.ts",
15
+ "!src/**/*.d.ts",
16
+ "!src/index.ts"
17
+ ],
18
+ "coverageDirectory": "coverage",
19
+ "coverageReporters": ["text", "lcov", "html"],
20
+ "reporters": [
21
+ "default",
22
+ ["jest-junit", { "outputDirectory": "coverage", "outputName": "junit.xml" }]
23
+ ],
24
+ "coverageThreshold": {
25
+ "global": {
26
+ "branches": 7,
27
+ "functions": 5,
28
+ "lines": 6,
29
+ "statements": 6
30
+ }
31
+ },
32
+ "setupFilesAfterEnv": ["<rootDir>/__tests__/setup.ts"],
33
+ "testTimeout": 10000,
34
+ "verbose": true,
35
+ "forceExit": true,
36
+ "detectOpenHandles": true
37
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "ci": {
3
+ "collect": {
4
+ "numberOfRuns": 3,
5
+ "startServerCommand": "npm run start",
6
+ "startServerReadyPattern": "Dhruv CLI is running",
7
+ "url": ["http://localhost:3000"]
8
+ },
9
+ "assert": {
10
+ "assertions": {
11
+ "categories:performance": ["error", {"minScore": 0.8}],
12
+ "categories:accessibility": ["error", {"minScore": 0.9}],
13
+ "categories:best-practices": ["error", {"minScore": 0.9}],
14
+ "categories:seo": ["error", {"minScore": 0.9}],
15
+ "categories:pwa": "off"
16
+ }
17
+ },
18
+ "upload": {
19
+ "target": "temporary-public-storage"
20
+ }
21
+ }
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rahul05ranjan/dhruv-cli",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "AI-powered CLI assistant for developers using Ollama",
5
5
  "keywords": [
6
6
  "ai",
@@ -21,7 +21,30 @@
21
21
  "scripts": {
22
22
  "build": "tsc",
23
23
  "start": "node ./dist/index.js",
24
- "dev": "ts-node src/index.ts"
24
+ "dev": "ts-node src/index.ts",
25
+ "test": "jest",
26
+ "test:watch": "jest --watch",
27
+ "test:coverage": "jest --coverage",
28
+ "test:ci": "jest --coverage --watchAll=false --passWithNoTests",
29
+ "test:e2e": "jest --config jest.e2e.config.js",
30
+ "lint": "eslint . --ext .ts,.js",
31
+ "lint:fix": "eslint . --ext .ts,.js --fix",
32
+ "type-check": "tsc --noEmit",
33
+ "docs": "typedoc --out docs/api src",
34
+ "docs:serve": "npx serve docs",
35
+ "docs:publish": "npm run docs && gh-pages -d docs",
36
+ "benchmark": "autocannon -c 100 -d 10 http://localhost:3000",
37
+ "performance": "lighthouse http://localhost:3000 --output=json --output-path=./reports/lighthouse.json",
38
+ "security": "npm audit --audit-level=moderate",
39
+ "security:full": "npm audit --audit-level=info",
40
+ "clean": "rm -rf dist coverage .dhruv-cache logs/*.log",
41
+ "prepublishOnly": "npm run clean && npm run lint && npm run test:ci && npm run build",
42
+ "release": "semantic-release",
43
+ "release:beta": "semantic-release --tag beta",
44
+ "docker:build": "docker build -t dhruv-cli .",
45
+ "docker:run": "docker run -it dhruv-cli",
46
+ "health": "node dist/index.js health",
47
+ "metrics": "node dist/index.js metrics"
25
48
  },
26
49
  "dependencies": {
27
50
  "chalk": "^5.3.0",
@@ -29,21 +52,52 @@
29
52
  "cli-progress": "^3.12.0",
30
53
  "commander": "^14.0.0",
31
54
  "inquirer": "^12.6.3",
32
- "node-fetch": "^2.7.0",
55
+ "joi": "^18.2.9",
56
+ "node-fetch": "^3.3.2",
33
57
  "ollama": "^0.5.16",
34
- "ora": "^8.2.0"
58
+ "ora": "^8.2.0",
59
+ "prom-client": "^15.1.3",
60
+ "validator": "^13.15.35",
61
+ "winston": "^3.17.0",
62
+ "winston-daily-rotate-file": "^5.0.0"
35
63
  },
36
64
  "devDependencies": {
37
- "@eslint/js": "^8.57.1",
65
+ "@eslint/js": "^9.30.1",
66
+ "@lhci/cli": "^0.15.1",
67
+ "@semantic-release/changelog": "^6.0.3",
68
+ "@semantic-release/commit-analyzer": "^12.0.0",
69
+ "@semantic-release/git": "^10.0.1",
70
+ "@semantic-release/github": "^10.3.5",
71
+ "@semantic-release/npm": "^13.1.5",
72
+ "@semantic-release/release-notes-generator": "^12.1.0",
38
73
  "@types/inquirer": "^9.0.8",
74
+ "@types/jest": "^30.0.0",
75
+ "@types/nock": "^10.0.3",
39
76
  "@types/node": "^24.0.7",
77
+ "@types/supertest": "^6.0.3",
78
+ "@types/validator": "^13.15.2",
40
79
  "@typescript-eslint/eslint-plugin": "^8.35.0",
41
80
  "@typescript-eslint/parser": "^8.35.0",
42
- "eslint": "^8.57.1",
43
- "semantic-release": "^23.1.1",
81
+ "autocannon": "^8.0.0",
82
+ "benchmark": "^2.1.4",
83
+ "eslint": "^9.30.0",
84
+ "istanbul-lib-coverage": "^3.2.2",
85
+ "istanbul-lib-report": "^3.0.1",
86
+ "istanbul-reports": "^3.2.0",
87
+ "jest": "^30.1.0",
88
+ "jest-junit": "^17.0.0",
89
+ "jsdoc-to-markdown": "^9.1.2",
90
+ "lighthouse": "^12.8.1",
91
+ "nock": "^14.0.10",
92
+ "semantic-release": "^25.0.1",
93
+ "supertest": "^7.1.4",
94
+ "ts-jest": "^29.4.1",
44
95
  "ts-node": "^10.9.1",
96
+ "typedoc": "^0.28.11",
97
+ "typedoc-plugin-markdown": "^4.8.1",
45
98
  "typescript": "^5.8.3",
46
- "typescript-eslint": "^8.35.0"
99
+ "typescript-eslint": "^8.35.0",
100
+ "yaml": "^2.9.1"
47
101
  },
48
102
  "repository": {
49
103
  "type": "git",
@@ -1,45 +1,16 @@
1
- import { askOllama } from '../core/ai.js';
2
- import ora from 'ora';
3
- import chalk from 'chalk';
4
- import { loadConfig } from '../config/config.js';
5
- import { highlightCode, printError } from '../utils/ux.js';
1
+ import { runCommand } from '../core/command-runner.js';
2
+ import { getSystemMessage } from '../core/prompts.js';
6
3
 
7
4
  export async function explain(query: string) {
8
- const config = loadConfig();
9
- const spinner = ora('Thinking...').start();
10
- let streamed = '';
11
- const dhruvIntro = chalk.yellowBright('Dhruv CLI: Your AI-powered CLI assistant for developers using Ollama.\n');
12
- try {
13
- spinner.stop();
14
- process.stdout.write(dhruvIntro); // Print Dhruv intro before explanation
15
- process.stdout.write(chalk.green('Explanation: '));
16
- await askOllama({
17
- prompt: `Explain: ${query}`,
18
- model: config.model,
19
- onToken: (token: string) => {
20
- streamed += token;
21
- process.stdout.write(chalk.cyan(token));
22
- }
23
- });
24
- process.stdout.write('\n');
25
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
26
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
27
- for (const block of codeBlocks) {
28
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
29
- if (code) {
30
- try {
31
- console.log(highlightCode(code, lang || 'js'));
32
- } catch (err) {
33
- // Only log highlight errors in development
34
- if (process.env.NODE_ENV === 'development') {
35
- console.error('Highlight error:', err);
36
- }
37
- }
38
- }
39
- }
40
- }
41
- } catch (err) {
42
- printError('Failed to get explanation.');
43
- console.error(chalk.red((err as Error).message));
44
- }
5
+ await runCommand({
6
+ name: 'explain',
7
+ input: { query },
8
+ header: '๐Ÿ“š Explanation: ',
9
+ buildRequest: (input, model) => ({
10
+ prompt: input.query,
11
+ systemMessage: getSystemMessage('explain'),
12
+ model,
13
+ }),
14
+ footer: `๐Ÿ’ก Need more help? Try: dhruv suggest "${query}"`,
15
+ });
45
16
  }
@@ -1,34 +1,16 @@
1
- import { askOllama } from '../core/ai.js';
2
- import ora from 'ora';
3
- import chalk from 'chalk';
4
- import { loadConfig } from '../config/config.js';
5
- import { highlightCode, printError } from '../utils/ux.js';
1
+ import { runCommand } from '../core/command-runner.js';
2
+ import { getSystemMessage } from '../core/prompts.js';
6
3
 
7
4
  export async function fix(query: string) {
8
- const config = loadConfig();
9
- const spinner = ora('Analyzing issue...').start();
10
- let streamed = '';
11
- try {
12
- spinner.stop();
13
- process.stdout.write(chalk.green('Fix suggestion: '));
14
- await askOllama({
15
- prompt: `Fix: ${query}`,
16
- model: config.model,
17
- onToken: (token: string) => {
18
- streamed += token;
19
- process.stdout.write(chalk.cyan(token));
20
- }
21
- });
22
- process.stdout.write('\n');
23
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
24
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
25
- for (const block of codeBlocks) {
26
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
27
- if (code) try { console.log(highlightCode(code, lang || 'js')); } catch (err) { console.error('Highlight error:', err); }
28
- }
29
- }
30
- } catch (err) {
31
- printError('Failed to get fix suggestion.');
32
- console.error(chalk.red((err as Error).message));
33
- }
5
+ await runCommand({
6
+ name: 'fix',
7
+ input: { query },
8
+ header: '๐Ÿ”ง Fix Analysis: ',
9
+ buildRequest: (input, model) => ({
10
+ prompt: input.query,
11
+ systemMessage: getSystemMessage('fix'),
12
+ model,
13
+ }),
14
+ footer: `๐Ÿงช Want to test this? Try: dhruv generate tests <your-file>`,
15
+ });
34
16
  }
@@ -1,55 +1,75 @@
1
- import { askOllama } from '../core/ai.js';
2
- import chalk from 'chalk';
3
- import { loadConfig } from '../config/config.js';
4
1
  import fs from 'fs';
5
- import { highlightCode, printError, printSuccess } from '../utils/ux.js';
2
+ import path from 'path';
3
+ import { runCommand } from '../core/command-runner.js';
4
+ import { getSystemMessage } from '../core/prompts.js';
5
+ import { printError, printSuccess, printInfo } from '../utils/ux.js';
6
+ import { loadConfig } from '../config/config.js';
6
7
 
7
- export async function generate(type: string, target: string) {
8
- const config = loadConfig();
9
- let content = '';
10
- if (fs.existsSync(target)) {
11
- content = fs.readFileSync(target, 'utf-8');
8
+ function buildPrompt(type: string, content: string): string {
9
+ if (type === 'tests' || type === 'test') {
10
+ return `Generate comprehensive unit tests for the following JavaScript code. Use Jest or Mocha syntax. Only return the test code without explanations:\n\n${content}`;
12
11
  }
13
- let streamed = '';
14
- try {
15
- process.stdout.write(chalk.green('Generated code: '));
16
- await askOllama({
17
- prompt: `Generate only a valid JavaScript ${type} file for this code, no explanations, no Markdown, just the code.\n${content}`,
18
- model: config.model,
19
- onToken: (token: string) => {
20
- streamed += token;
21
- process.stdout.write(chalk.cyan(token));
12
+ if (type === 'documentation' || type === 'docs') {
13
+ return `Generate JSDoc documentation for the following code:\n\n${content}`;
14
+ }
15
+ return `Generate ${type} for this code:\n\n${content}`;
16
+ }
17
+
18
+ /** Extracts test code from the response: a fenced block if present, else the raw response. */
19
+ function extractTestCode(response: string): string {
20
+ const fenced = response.match(/```(?:javascript|js)?\s*\n([\s\S]*?)```/);
21
+ if (fenced?.[1]) return fenced[1].trim();
22
+ return response
23
+ .replace(/^.*?(?=const|describe|test|it\s*\()/s, '')
24
+ .replace(/```[a-z]*\n?/g, '')
25
+ .trim();
26
+ }
27
+
28
+ export interface GenerateOptions {
29
+ apply?: boolean;
30
+ output?: string;
31
+ overwrite?: boolean;
32
+ }
33
+
34
+ export async function generate(type: string, target: string, options: GenerateOptions = {}) {
35
+ if (!fs.existsSync(target)) {
36
+ printError(`Target file "${target}" does not exist.`);
37
+ return;
38
+ }
39
+
40
+ const content = fs.readFileSync(target, 'utf-8');
41
+
42
+ await runCommand({
43
+ name: 'generate',
44
+ input: { type, target },
45
+ header: `๐Ÿ”จ Generating ${type}: `,
46
+ buildRequest: (input, model) => ({
47
+ prompt: buildPrompt(input.type, content),
48
+ systemMessage: getSystemMessage('generate'),
49
+ model,
50
+ }),
51
+ onComplete: (response) => {
52
+ if (type !== 'tests' && type !== 'test') return;
53
+ const codeToSave = extractTestCode(response);
54
+ if (!codeToSave) {
55
+ printError('No valid test code generated.');
56
+ return;
22
57
  }
23
- });
24
- process.stdout.write('\n');
25
- // Robust code extraction for tests
26
- let codeToSave = streamed;
27
- if (type === 'tests' && target && typeof streamed === 'string') {
28
- const codeBlockMatch = streamed.match(/```(?:[a-z]*)?\n([\s\S]*?)```/);
29
- if (codeBlockMatch && codeBlockMatch[1]) {
30
- codeToSave = codeBlockMatch[1].trim();
31
- } else {
32
- // Remove Markdown, explanations, and keep only lines that look like code
33
- codeToSave = streamed
34
- .replace(/```[a-z]*\n|```/g, '') // remove code block markers
35
- .split('\n')
36
- .filter(line => line.trim() && !/^\s*#|^\s*\/\//.test(line) && /[;{}()=]/.test(line))
37
- .join('\n')
38
- .trim();
58
+ const extension = path.extname(target) || '.js';
59
+ const testFile = options.output ?? target.replace(/\.[^.]+$/, `.test${extension}`);
60
+ if (!options.apply && !options.output) {
61
+ if (loadConfig().responseFormat !== 'json') {
62
+ printInfo(`Preview only. Use --apply to write ${testFile}, or --output <path> to choose a destination.`);
63
+ }
64
+ return;
39
65
  }
40
- const testFile = target.replace(/\.[^.]+$/, '.test.js');
41
- fs.writeFileSync(testFile, codeToSave);
42
- printSuccess(`Test file saved: ${testFile}`);
43
- }
44
- if (typeof highlightCode === 'function' && streamed.match(/```[a-z]*[\s\S]*?```/)) {
45
- const codeBlocks = streamed.match(/```([a-z]*)\n([\s\S]*?)```/g) || [];
46
- for (const block of codeBlocks) {
47
- const [, lang, code] = block.match(/```([a-z]*)\n([\s\S]*?)```/) || [];
48
- if (code) try { console.log(highlightCode(code, lang || 'js')); } catch (err) { console.error('Highlight error:', err); }
66
+ if (fs.existsSync(testFile) && !options.overwrite) {
67
+ printError(`Test file "${testFile}" already exists. Use --overwrite to replace it.`);
68
+ return;
49
69
  }
50
- }
51
- } catch (err) {
52
- printError('Failed to generate code.');
53
- console.error(chalk.red((err as Error).message));
54
- }
70
+ fs.writeFileSync(testFile, codeToSave);
71
+ if (loadConfig().responseFormat !== 'json') printSuccess(`Test file saved: ${testFile}`);
72
+ },
73
+ footer: `๐Ÿ” Want a review? Try: dhruv review ${target}`,
74
+ });
55
75
  }