ai-unit-test-generator 2.0.4 โ 2.0.6
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/CHANGELOG.md +60 -0
- package/lib/ai/client.mjs +2 -1
- package/lib/ai/reviewer.mjs +135 -169
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
@@ -5,6 +5,66 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [2.0.6] - 2025-01-11
|
9
|
+
|
10
|
+
### ๐ Hotfix
|
11
|
+
- **Fixed**: `ai-test generate` command - corrected `client.mjs` argument parsing
|
12
|
+
- Now supports `--prompt`, `--prompt-file`, and `--promptFile` for compatibility
|
13
|
+
- Resolves `Prompt file not found: prompt.txt` error in batch generation workflow
|
14
|
+
|
15
|
+
---
|
16
|
+
|
17
|
+
## [2.0.5] - 2025-01-11
|
18
|
+
|
19
|
+
### โจ Feature: Simplified AI Suggestion Review
|
20
|
+
|
21
|
+
**Problem**: Previous review UX was too complex, requiring category-by-category selection.
|
22
|
+
|
23
|
+
**Solution**: Completely redesigned `lib/ai/reviewer.mjs` with:
|
24
|
+
- โ
**One-click Accept All** (`a`) - instantly accept all AI suggestions
|
25
|
+
- โ
**One-click Reject All** (`r`) - instantly reject all suggestions
|
26
|
+
- โ
**Partial Accept** (input numbers like `1,3,5`) - granular control
|
27
|
+
- โ
**Compact Display** - all suggestions shown at once with global indexing
|
28
|
+
- โ
**Clear Summary** - final acceptance count before applying changes
|
29
|
+
|
30
|
+
**User Experience**:
|
31
|
+
```bash
|
32
|
+
ai-test analyze
|
33
|
+
|
34
|
+
# AI ๅๆๅฎๆๅ็ซๅณๅฑ็คบๆๆๅปบ่ฎฎ๏ผ
|
35
|
+
๐ค AI Analysis Results: 12 suggestions
|
36
|
+
|
37
|
+
๐ด Business Critical Paths (5):
|
38
|
+
[1] services/payment/** | BC=10 | Conf: 95%
|
39
|
+
โ Handles Stripe payment processing
|
40
|
+
[2] ...
|
41
|
+
|
42
|
+
โ ๏ธ High Risk Modules (4):
|
43
|
+
[6] utils/date/** | ER=8 | Conf: 88%
|
44
|
+
โ Complex timezone calculations
|
45
|
+
[7] ...
|
46
|
+
|
47
|
+
โ
Testability Adjustments (3):
|
48
|
+
[10] utils/** | Adj=+1 | Conf: 92%
|
49
|
+
โ Pure functions, easy to test
|
50
|
+
|
51
|
+
โ Choose action:
|
52
|
+
[a] Accept all 12 suggestions
|
53
|
+
[r] Reject all
|
54
|
+
Or input numbers (comma-separated, e.g. 1,3,5-8)
|
55
|
+
|
56
|
+
> a # ๆ r๏ผๆ 1,3,5
|
57
|
+
```
|
58
|
+
|
59
|
+
**Changes**:
|
60
|
+
- Removed multi-stage category review loop
|
61
|
+
- Added global indexing across all categories
|
62
|
+
- Simplified user input parsing (a/r/numbers only)
|
63
|
+
- Removed per-category score adjustment (can be done manually after if needed)
|
64
|
+
- Single confirmation step at the end
|
65
|
+
|
66
|
+
---
|
67
|
+
|
8
68
|
## [2.0.4] - 2025-01-11
|
9
69
|
|
10
70
|
### ๐ Hotfix
|
package/lib/ai/client.mjs
CHANGED
@@ -60,7 +60,8 @@ export async function runOnce({ prompt, promptFile, out = 'reports/ai_response.t
|
|
60
60
|
|
61
61
|
export async function runCLI(argv = process.argv) {
|
62
62
|
const args = parseArgs(argv)
|
63
|
-
|
63
|
+
// ๆฏๆ --prompt, --prompt-file, --promptFile ไธ็งๅฝขๅผ
|
64
|
+
const promptFile = args['prompt'] || args['prompt-file'] || args['promptFile'] || null
|
64
65
|
const out = args['out'] || 'reports/ai_response.txt'
|
65
66
|
const model = args['model'] || null
|
66
67
|
const timeoutSec = args['timeout'] ? Number(args['timeout']) : 600
|
package/lib/ai/reviewer.mjs
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
2
|
* ไบคไบๅผ AI ๅปบ่ฎฎๅฎกๆ ธๅจ
|
3
|
-
*
|
3
|
+
* ๆฏๆ๏ผไธ้ฎๅ
จๆฅๅใไธ้ฎๅ
จๆ็ปใ้จๅๆฅๅ๏ผ่พๆฐๅญ๏ผ
|
4
4
|
*/
|
5
5
|
|
6
6
|
import readline from 'readline'
|
@@ -47,234 +47,200 @@ function getCategoryName(category) {
|
|
47
47
|
}
|
48
48
|
|
49
49
|
/**
|
50
|
-
*
|
51
|
-
*/
|
52
|
-
function getCategoryNameLower(category) {
|
53
|
-
const names = {
|
54
|
-
businessCriticalPaths: 'business critical paths',
|
55
|
-
highRiskModules: 'high risk modules',
|
56
|
-
testabilityAdjustments: 'testability adjustments'
|
57
|
-
}
|
58
|
-
return names[category] || category
|
59
|
-
}
|
60
|
-
|
61
|
-
/**
|
62
|
-
* ๆ ผๅผๅๅไธชๅปบ่ฎฎ
|
50
|
+
* ๆ ผๅผๅๅไธชๅปบ่ฎฎ๏ผ็ดงๅๆ ผๅผ๏ผ
|
63
51
|
*/
|
64
52
|
function formatSuggestion(item, index, category) {
|
65
|
-
let
|
53
|
+
let scoreInfo = ''
|
66
54
|
|
67
55
|
if (category === 'businessCriticalPaths') {
|
68
|
-
|
56
|
+
scoreInfo = `BC=${item.suggestedBC}`
|
69
57
|
} else if (category === 'highRiskModules') {
|
70
|
-
|
58
|
+
scoreInfo = `ER=${item.suggestedER}`
|
71
59
|
} else if (category === 'testabilityAdjustments') {
|
72
|
-
|
60
|
+
scoreInfo = `Adj=${item.adjustment}`
|
73
61
|
}
|
74
62
|
|
75
|
-
|
76
|
-
output += ` Evidence:\n`
|
77
|
-
item.evidence.forEach(e => {
|
78
|
-
output += ` - ${e}\n`
|
79
|
-
})
|
80
|
-
|
81
|
-
return output
|
82
|
-
}
|
83
|
-
|
84
|
-
/**
|
85
|
-
* ๆพ็คบๅปบ่ฎฎๅ่กจ
|
86
|
-
*/
|
87
|
-
function displaySuggestions(category, items) {
|
88
|
-
const icon = getCategoryIcon(category)
|
89
|
-
const name = getCategoryName(category)
|
90
|
-
|
91
|
-
console.log(`\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n`)
|
92
|
-
console.log(`${icon} ${name} (${items.length} suggestions):`)
|
93
|
-
|
94
|
-
items.forEach((item, i) => {
|
95
|
-
console.log(formatSuggestion(item, i + 1, category))
|
96
|
-
})
|
97
|
-
|
98
|
-
console.log(`\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n`)
|
99
|
-
}
|
100
|
-
|
101
|
-
/**
|
102
|
-
* ่งฃๆ็จๆท้ๆฉ
|
103
|
-
*/
|
104
|
-
function parseSelection(input, maxCount) {
|
105
|
-
if (!input || input.trim() === '') {
|
106
|
-
return []
|
107
|
-
}
|
63
|
+
const confidence = `${(item.confidence * 100).toFixed(0)}%`
|
108
64
|
|
109
|
-
return
|
110
|
-
.map(s => parseInt(s.trim()))
|
111
|
-
.filter(n => !isNaN(n) && n >= 1 && n <= maxCount)
|
65
|
+
return ` [${index}] ${item.pattern} | ${scoreInfo} | Conf: ${confidence}\n โ ${item.reason}`
|
112
66
|
}
|
113
67
|
|
114
68
|
/**
|
115
|
-
*
|
69
|
+
* ๆพ็คบๆๆๅปบ่ฎฎ๏ผ็ดงๅ่งๅพ๏ผ
|
116
70
|
*/
|
117
|
-
|
118
|
-
|
71
|
+
function displayAllSuggestions(validated) {
|
72
|
+
const categories = ['businessCriticalPaths', 'highRiskModules', 'testabilityAdjustments']
|
73
|
+
const totalSuggestions = Object.values(validated).reduce((sum, arr) => sum + arr.length, 0)
|
119
74
|
|
120
|
-
|
121
|
-
|
122
|
-
const maxScore = 10
|
75
|
+
console.log(`\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`)
|
76
|
+
console.log(`\n๐ค AI Analysis Results: ${totalSuggestions} suggestions\n`)
|
123
77
|
|
124
|
-
|
78
|
+
let globalIndex = 1
|
79
|
+
const indexMapping = [] // [{ globalIndex, category, localIndex }, ...]
|
125
80
|
|
126
|
-
for (
|
127
|
-
const
|
128
|
-
|
81
|
+
for (const category of categories) {
|
82
|
+
const items = validated[category] || []
|
83
|
+
if (items.length === 0) continue
|
129
84
|
|
130
|
-
const
|
85
|
+
const icon = getCategoryIcon(category)
|
86
|
+
const name = getCategoryName(category)
|
131
87
|
|
132
|
-
|
133
|
-
const newScore = parseInt(input.trim())
|
134
|
-
if (!isNaN(newScore) && newScore >= minScore && newScore <= maxScore) {
|
135
|
-
item[scoreField] = newScore
|
136
|
-
console.log(` โ
Updated: ${currentScore} โ ${newScore}`)
|
137
|
-
} else {
|
138
|
-
console.log(` โ ๏ธ Invalid score (must be ${minScore}-${maxScore}), keeping original`)
|
139
|
-
}
|
140
|
-
}
|
88
|
+
console.log(`\n${icon} ${name} (${items.length}):`)
|
141
89
|
|
142
|
-
|
90
|
+
items.forEach((item, localIndex) => {
|
91
|
+
console.log(formatSuggestion(item, globalIndex, category))
|
92
|
+
indexMapping.push({ globalIndex, category, localIndex })
|
93
|
+
globalIndex++
|
94
|
+
})
|
143
95
|
}
|
144
96
|
|
145
|
-
|
97
|
+
console.log(`\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n`)
|
98
|
+
|
99
|
+
return { totalSuggestions, indexMapping }
|
146
100
|
}
|
147
101
|
|
148
102
|
/**
|
149
|
-
*
|
103
|
+
* ่งฃๆ็จๆท่พๅ
ฅ
|
150
104
|
*/
|
151
|
-
|
152
|
-
|
153
|
-
return []
|
154
|
-
}
|
105
|
+
function parseUserInput(input, totalCount) {
|
106
|
+
const trimmed = input.trim().toLowerCase()
|
155
107
|
|
156
|
-
//
|
157
|
-
|
108
|
+
// ๅ
จๆฅๅ
|
109
|
+
if (trimmed === 'a' || trimmed === 'all') {
|
110
|
+
return { type: 'accept_all' }
|
111
|
+
}
|
158
112
|
|
159
|
-
//
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
` [a] Accept all (${items.length})\n` +
|
164
|
-
` [r] Reject all\n` +
|
165
|
-
` [s] Select individually\n` +
|
166
|
-
` [n] Skip (keep empty)\n` +
|
167
|
-
`> `
|
168
|
-
)
|
113
|
+
// ๅ
จๆ็ป
|
114
|
+
if (trimmed === 'r' || trimmed === 'reject') {
|
115
|
+
return { type: 'reject_all' }
|
116
|
+
}
|
169
117
|
|
170
|
-
|
118
|
+
// ้จๅๆฅๅ๏ผๆฐๅญๅ่กจ๏ผ
|
119
|
+
const numbers = input.split(',')
|
120
|
+
.map(s => parseInt(s.trim()))
|
121
|
+
.filter(n => !isNaN(n) && n >= 1 && n <= totalCount)
|
171
122
|
|
172
|
-
if (
|
173
|
-
|
174
|
-
console.log(`โ
Accepted: ${items.length}/${items.length} ${categoryName}`)
|
175
|
-
|
176
|
-
// ่ฏข้ฎๆฏๅฆ่ฐๆดๅๆฐ
|
177
|
-
if (category === 'businessCriticalPaths' || category === 'highRiskModules') {
|
178
|
-
const adjustInput = await ask(rl, `\nAdjust scores? (y/n) `)
|
179
|
-
if (adjustInput.trim().toLowerCase() === 'y') {
|
180
|
-
return await adjustScores(rl, items, category)
|
181
|
-
}
|
182
|
-
}
|
183
|
-
|
184
|
-
return items
|
185
|
-
} else if (actionLower === 's') {
|
186
|
-
// ้ๆก้ๆฉ
|
187
|
-
const selection = await ask(rl, `\nSelect which suggestions to accept (comma-separated, e.g. 1,3):\n> `)
|
188
|
-
const indices = parseSelection(selection, items.length)
|
189
|
-
|
190
|
-
if (indices.length === 0) {
|
191
|
-
console.log(`โ No suggestions selected`)
|
192
|
-
return []
|
193
|
-
}
|
194
|
-
|
195
|
-
const selected = indices.map(i => items[i - 1])
|
196
|
-
console.log(`โ
Selected: ${selected.length}/${items.length} ${categoryName}`)
|
197
|
-
|
198
|
-
// ่ฏข้ฎๆฏๅฆ่ฐๆดๅๆฐ
|
199
|
-
if (category === 'businessCriticalPaths' || category === 'highRiskModules') {
|
200
|
-
const adjustInput = await ask(rl, `\nAdjust scores? (y/n) `)
|
201
|
-
if (adjustInput.trim().toLowerCase() === 'y') {
|
202
|
-
return await adjustScores(rl, selected, category)
|
203
|
-
}
|
204
|
-
}
|
205
|
-
|
206
|
-
return selected
|
207
|
-
} else if (actionLower === 'r') {
|
208
|
-
// ๆ็ปๅ
จ้จ
|
209
|
-
console.log(`โ Rejected: all ${categoryName}`)
|
210
|
-
return []
|
211
|
-
} else {
|
212
|
-
// ่ทณ่ฟ
|
213
|
-
console.log(`โญ๏ธ Skipped ${categoryName}`)
|
214
|
-
return []
|
123
|
+
if (numbers.length > 0) {
|
124
|
+
return { type: 'partial', indices: numbers }
|
215
125
|
}
|
126
|
+
|
127
|
+
return { type: 'invalid' }
|
216
128
|
}
|
217
129
|
|
218
130
|
/**
|
219
|
-
*
|
131
|
+
* ๆพ็คบๆ็ปๆป็ป
|
220
132
|
*/
|
221
|
-
function
|
133
|
+
function displayFinalSummary(result, validated) {
|
222
134
|
const totalSuggested = Object.values(validated).reduce((sum, arr) => sum + arr.length, 0)
|
223
135
|
const totalAccepted = Object.values(result).reduce((sum, arr) => sum + arr.length, 0)
|
224
136
|
|
225
|
-
console.log(`\n
|
226
|
-
console.log(
|
227
|
-
console.log(` Business Critical Paths: ${result.businessCriticalPaths.length}/${validated.businessCriticalPaths?.length || 0}
|
228
|
-
console.log(` High Risk Modules: ${result.highRiskModules.length}/${validated.highRiskModules?.length || 0}
|
229
|
-
console.log(` Testability Adjustments: ${result.testabilityAdjustments.length}/${validated.testabilityAdjustments?.length || 0}
|
230
|
-
console.log(` Total: ${totalAccepted}/${totalSuggested}
|
137
|
+
console.log(`\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`)
|
138
|
+
console.log(`\n๐ Final Summary:`)
|
139
|
+
console.log(` ๐ด Business Critical Paths: ${result.businessCriticalPaths.length}/${validated.businessCriticalPaths?.length || 0}`)
|
140
|
+
console.log(` โ ๏ธ High Risk Modules: ${result.highRiskModules.length}/${validated.highRiskModules?.length || 0}`)
|
141
|
+
console.log(` โ
Testability Adjustments: ${result.testabilityAdjustments.length}/${validated.testabilityAdjustments?.length || 0}`)
|
142
|
+
console.log(` Total: ${totalAccepted}/${totalSuggested} accepted\n`)
|
231
143
|
|
232
|
-
if (totalAccepted
|
233
|
-
console.log(
|
144
|
+
if (totalAccepted > 0) {
|
145
|
+
console.log(`๐ก These suggestions will be added to ai-test.config.jsonc`)
|
146
|
+
console.log(` and will take effect on next: ai-test scan`)
|
234
147
|
}
|
148
|
+
|
149
|
+
console.log(`\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n`)
|
235
150
|
}
|
236
151
|
|
237
152
|
/**
|
238
|
-
*
|
153
|
+
* ไบคไบๅผๅฎกๆ ธ๏ผ็ฎๅ็๏ผ
|
239
154
|
* @param {Object} validated - ๅทฒ้ช่ฏ็ๅปบ่ฎฎ
|
240
155
|
* @returns {Object|null} - ็จๆทๆนๅ็ๅปบ่ฎฎ๏ผๆ null๏ผๅๆถ๏ผ
|
241
156
|
*/
|
242
157
|
export async function interactiveReview(validated) {
|
243
158
|
const rl = createInterface()
|
244
159
|
|
245
|
-
const result = {
|
246
|
-
businessCriticalPaths: [],
|
247
|
-
highRiskModules: [],
|
248
|
-
testabilityAdjustments: []
|
249
|
-
}
|
250
|
-
|
251
160
|
try {
|
252
|
-
//
|
253
|
-
const
|
161
|
+
// 1. ๆพ็คบๆๆๅปบ่ฎฎ
|
162
|
+
const { totalSuggestions, indexMapping } = displayAllSuggestions(validated)
|
254
163
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
result[category] = await reviewCategory(rl, category, items)
|
164
|
+
if (totalSuggestions === 0) {
|
165
|
+
console.log('โ ๏ธ No suggestions to review.')
|
166
|
+
rl.close()
|
167
|
+
return null
|
260
168
|
}
|
261
169
|
|
262
|
-
//
|
263
|
-
|
264
|
-
|
265
|
-
|
170
|
+
// 2. ่ฏข้ฎ็จๆทๆไฝ
|
171
|
+
const userInput = await ask(rl,
|
172
|
+
`โ Choose action:\n` +
|
173
|
+
` [a] Accept all ${totalSuggestions} suggestions\n` +
|
174
|
+
` [r] Reject all\n` +
|
175
|
+
` Or input numbers (comma-separated, e.g. 1,3,5-8)\n` +
|
176
|
+
`\n> `
|
177
|
+
)
|
178
|
+
|
179
|
+
const parsed = parseUserInput(userInput, totalSuggestions)
|
180
|
+
|
181
|
+
// 3. ๅค็็จๆท้ๆฉ
|
182
|
+
const result = {
|
183
|
+
businessCriticalPaths: [],
|
184
|
+
highRiskModules: [],
|
185
|
+
testabilityAdjustments: []
|
186
|
+
}
|
266
187
|
|
267
|
-
if (
|
188
|
+
if (parsed.type === 'accept_all') {
|
189
|
+
// ๅ
จๆฅๅ
|
190
|
+
result.businessCriticalPaths = validated.businessCriticalPaths || []
|
191
|
+
result.highRiskModules = validated.highRiskModules || []
|
192
|
+
result.testabilityAdjustments = validated.testabilityAdjustments || []
|
193
|
+
|
194
|
+
console.log(`\nโ
Accepted all ${totalSuggestions} suggestions`)
|
195
|
+
|
196
|
+
} else if (parsed.type === 'reject_all') {
|
197
|
+
// ๅ
จๆ็ป
|
198
|
+
console.log(`\nโ Rejected all suggestions`)
|
199
|
+
rl.close()
|
200
|
+
return null
|
201
|
+
|
202
|
+
} else if (parsed.type === 'partial') {
|
203
|
+
// ้จๅๆฅๅ
|
204
|
+
const selectedIndices = new Set(parsed.indices)
|
205
|
+
|
206
|
+
indexMapping.forEach(({ globalIndex, category, localIndex }) => {
|
207
|
+
if (selectedIndices.has(globalIndex)) {
|
208
|
+
const item = validated[category][localIndex]
|
209
|
+
result[category].push(item)
|
210
|
+
}
|
211
|
+
})
|
212
|
+
|
213
|
+
const totalAccepted = Object.values(result).reduce((sum, arr) => sum + arr.length, 0)
|
214
|
+
console.log(`\nโ
Accepted ${totalAccepted}/${totalSuggestions} suggestions`)
|
215
|
+
|
216
|
+
if (totalAccepted === 0) {
|
217
|
+
console.log(`โ ๏ธ No valid suggestions selected`)
|
218
|
+
rl.close()
|
219
|
+
return null
|
220
|
+
}
|
221
|
+
|
222
|
+
} else {
|
223
|
+
// ๆ ๆ่พๅ
ฅ
|
224
|
+
console.log(`\nโ Invalid input. No changes made.`)
|
268
225
|
rl.close()
|
269
226
|
return null
|
270
227
|
}
|
271
228
|
|
272
|
-
//
|
273
|
-
|
229
|
+
// 4. ๆพ็คบๆ็ปๆป็ป
|
230
|
+
displayFinalSummary(result, validated)
|
231
|
+
|
232
|
+
// 5. ๆ็ป็กฎ่ฎค
|
233
|
+
const confirm = await ask(rl, `๐พ Apply these changes? (y/n)\n> `)
|
274
234
|
|
275
235
|
rl.close()
|
276
236
|
|
277
|
-
|
237
|
+
if (confirm.trim().toLowerCase() === 'y') {
|
238
|
+
return result
|
239
|
+
} else {
|
240
|
+
console.log(`\nโ Changes discarded.`)
|
241
|
+
return null
|
242
|
+
}
|
243
|
+
|
278
244
|
} catch (err) {
|
279
245
|
rl.close()
|
280
246
|
throw err
|