@amirdaraee/namewise 0.5.3 → 0.5.5

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 (83) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +60 -60
  3. package/dist/index.js +0 -0
  4. package/dist/services/claude-service.d.ts.map +1 -1
  5. package/dist/services/claude-service.js +3 -0
  6. package/dist/services/claude-service.js.map +1 -1
  7. package/dist/services/lmstudio-service.d.ts +1 -0
  8. package/dist/services/lmstudio-service.d.ts.map +1 -1
  9. package/dist/services/lmstudio-service.js +16 -1
  10. package/dist/services/lmstudio-service.js.map +1 -1
  11. package/dist/services/ollama-service.d.ts +1 -0
  12. package/dist/services/ollama-service.d.ts.map +1 -1
  13. package/dist/services/ollama-service.js +16 -1
  14. package/dist/services/ollama-service.js.map +1 -1
  15. package/dist/services/openai-service.d.ts.map +1 -1
  16. package/dist/services/openai-service.js +3 -0
  17. package/dist/services/openai-service.js.map +1 -1
  18. package/package.json +8 -8
  19. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -82
  20. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -61
  21. package/.github/workflows/auto-release.yml +0 -81
  22. package/.github/workflows/build.yml +0 -55
  23. package/.github/workflows/publish.yml +0 -134
  24. package/.github/workflows/test.yml +0 -45
  25. package/eng.traineddata +0 -0
  26. package/src/cli/commands.ts +0 -64
  27. package/src/cli/rename.ts +0 -171
  28. package/src/index.ts +0 -54
  29. package/src/parsers/excel-parser.ts +0 -66
  30. package/src/parsers/factory.ts +0 -38
  31. package/src/parsers/pdf-parser.ts +0 -99
  32. package/src/parsers/text-parser.ts +0 -43
  33. package/src/parsers/word-parser.ts +0 -50
  34. package/src/services/ai-factory.ts +0 -39
  35. package/src/services/claude-service.ts +0 -119
  36. package/src/services/file-renamer.ts +0 -141
  37. package/src/services/lmstudio-service.ts +0 -161
  38. package/src/services/ollama-service.ts +0 -191
  39. package/src/services/openai-service.ts +0 -117
  40. package/src/types/index.ts +0 -76
  41. package/src/types/pdf-extraction.d.ts +0 -7
  42. package/src/utils/ai-prompts.ts +0 -76
  43. package/src/utils/file-templates.ts +0 -275
  44. package/src/utils/naming-conventions.ts +0 -67
  45. package/src/utils/pdf-to-image.ts +0 -137
  46. package/tests/data/console-test-1.txt +0 -1
  47. package/tests/data/console-test-2.txt +0 -1
  48. package/tests/data/console-test-long-filename-for-display-testing.txt +0 -1
  49. package/tests/data/empty-file.txt +0 -0
  50. package/tests/data/failure.txt +0 -1
  51. package/tests/data/file1.txt +0 -1
  52. package/tests/data/file2.txt +0 -1
  53. package/tests/data/much-longer-filename-to-test-clearing.txt +0 -1
  54. package/tests/data/sample-markdown.md +0 -9
  55. package/tests/data/sample-pdf.pdf +0 -0
  56. package/tests/data/sample-text.txt +0 -25
  57. package/tests/data/short.txt +0 -1
  58. package/tests/data/single-file.txt +0 -1
  59. package/tests/data/success.txt +0 -1
  60. package/tests/data/this-is-a-very-long-filename-that-should-be-truncated-for-better-display-purposes.txt +0 -1
  61. package/tests/data/very-long-filename-that-should-be-cleared-properly.txt +0 -1
  62. package/tests/data/x.txt +0 -1
  63. package/tests/integration/ai-prompting.test.ts +0 -386
  64. package/tests/integration/end-to-end.test.ts +0 -209
  65. package/tests/integration/person-name-extraction.test.ts +0 -440
  66. package/tests/integration/workflow.test.ts +0 -336
  67. package/tests/mocks/mock-ai-service.ts +0 -58
  68. package/tests/unit/cli/commands.test.ts +0 -169
  69. package/tests/unit/parsers/factory.test.ts +0 -100
  70. package/tests/unit/parsers/pdf-parser.test.ts +0 -63
  71. package/tests/unit/parsers/text-parser.test.ts +0 -85
  72. package/tests/unit/services/ai-factory.test.ts +0 -85
  73. package/tests/unit/services/claude-service.test.ts +0 -188
  74. package/tests/unit/services/file-renamer.test.ts +0 -514
  75. package/tests/unit/services/lmstudio-service.test.ts +0 -326
  76. package/tests/unit/services/ollama-service.test.ts +0 -264
  77. package/tests/unit/services/openai-service.test.ts +0 -196
  78. package/tests/unit/utils/ai-prompts.test.ts +0 -213
  79. package/tests/unit/utils/file-templates.test.ts +0 -199
  80. package/tests/unit/utils/naming-conventions.test.ts +0 -88
  81. package/tests/unit/utils/pdf-to-image.test.ts +0 -127
  82. package/tsconfig.json +0 -20
  83. package/vitest.config.ts +0 -30
@@ -1,440 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { FileRenamer } from '../../src/services/file-renamer.js';
3
- import { DocumentParserFactory } from '../../src/parsers/factory.js';
4
- import { AIProvider, FileInfo, Config } from '../../src/types/index.js';
5
-
6
- // Sophisticated Mock AI that simulates person name extraction behavior
7
- class PersonNameExtractionMockAI implements AIProvider {
8
- name = 'PersonNameExtractionMock';
9
-
10
- async generateFileName(
11
- content: string,
12
- originalName: string,
13
- namingConvention = 'kebab-case',
14
- category = 'general',
15
- fileInfo?: FileInfo
16
- ): Promise<string> {
17
- // Simulate intelligent person name extraction from content
18
- const personNamePatterns = [
19
- // Visa applications
20
- /(?:visa.*?application.*?for|application.*?for.*?visa.*?for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i,
21
- // Contracts
22
- /(?:contract.*?between.*?and|agreement.*?with|employment.*?of)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i,
23
- // General "for [Name]" patterns
24
- /\bfor\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/,
25
- // Medical records
26
- /(?:patient|medical.*?record.*?for|treatment.*?for)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i,
27
- // Certificates
28
- /(?:certificate.*?for|awarded.*?to|issued.*?to)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/i
29
- ];
30
-
31
- let extractedName = '';
32
-
33
- for (const pattern of personNamePatterns) {
34
- const match = content.match(pattern);
35
- if (match && match[1]) {
36
- extractedName = match[1].toLowerCase().replace(/\s+/g, '-');
37
- break;
38
- }
39
- }
40
-
41
- // Check for irrelevant folder names and ignore them
42
- const irrelevantFolders = ['no', 'temp', 'downloads', 'misc', 'other', 'files'];
43
- const folderName = fileInfo?.parentFolder?.toLowerCase();
44
- const shouldIgnoreFolder = folderName && irrelevantFolders.includes(folderName);
45
-
46
- // Generate appropriate filename based on content and detected person
47
- if (content.toLowerCase().includes('visa') && content.toLowerCase().includes('application')) {
48
- const baseFilename = 'visitor-visa-application-for-family-members-in-canada';
49
- return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
50
- }
51
-
52
- if (content.toLowerCase().includes('contract') || content.toLowerCase().includes('employment')) {
53
- const baseFilename = 'employment-contract-software-engineer';
54
- return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
55
- }
56
-
57
- if (content.toLowerCase().includes('medical') || content.toLowerCase().includes('health')) {
58
- const baseFilename = 'medical-record-annual-checkup';
59
- return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
60
- }
61
-
62
- if (content.toLowerCase().includes('certificate') || content.toLowerCase().includes('diploma')) {
63
- const baseFilename = 'certificate-completion-course';
64
- return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
65
- }
66
-
67
- if (content.toLowerCase().includes('wedding') || content.toLowerCase().includes('marriage')) {
68
- const baseFilename = 'wedding-ceremony-invitation';
69
- return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
70
- }
71
-
72
- // Generic document
73
- const baseFilename = 'document-summary-report';
74
- return extractedName ? `${extractedName}-${baseFilename}` : baseFilename;
75
- }
76
- }
77
-
78
- describe('Person Name Extraction and Folder Filtering Integration Tests', () => {
79
- let mockAI: PersonNameExtractionMockAI;
80
- let fileRenamer: FileRenamer;
81
- let config: Config;
82
-
83
- beforeEach(() => {
84
- mockAI = new PersonNameExtractionMockAI();
85
- const parserFactory = new DocumentParserFactory();
86
-
87
- config = {
88
- provider: 'claude',
89
- apiKey: 'test-key',
90
- maxFileSize: 10 * 1024 * 1024,
91
- namingConvention: 'kebab-case',
92
- templateOptions: {
93
- category: 'document',
94
- personalName: '',
95
- dateFormat: 'YYYY-MM-DD'
96
- },
97
- dryRun: true
98
- };
99
-
100
- fileRenamer = new FileRenamer(parserFactory, mockAI, config);
101
- });
102
-
103
- describe('Person Name Extraction from Document Content', () => {
104
- it('should extract name from visa application content - Setareh case', async () => {
105
- const fileInfo: FileInfo = {
106
- name: 'visitor-visa-application-for-family-in-canada.pdf',
107
- path: '/no/visitor-visa-application-for-family-in-canada.pdf',
108
- extension: '.pdf',
109
- size: 1024 * 100,
110
- createdAt: new Date('2024-01-15'),
111
- modifiedAt: new Date('2024-02-01'),
112
- parentFolder: 'no', // Should be ignored
113
- folderPath: ['home', 'downloads', 'no']
114
- };
115
-
116
- const mockParser = {
117
- parse: vi.fn().mockResolvedValue({
118
- content: 'Visitor visa application for Setareh Ahmadi and family members to visit Canada. This application includes all required documentation for tourism and family visit purposes. The applicant Setareh Ahmadi is requesting permission to enter Canada.',
119
- metadata: {
120
- title: 'Visitor Visa Application',
121
- pages: 4
122
- }
123
- })
124
- };
125
-
126
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
127
-
128
- const results = await fileRenamer.renameFiles([fileInfo]);
129
-
130
- expect(results[0].success).toBe(true);
131
- // Should start with the detected person name and include visa/application terms
132
- expect(results[0].suggestedName).toMatch(/^setareh/);
133
- expect(results[0].suggestedName).toMatch(/visa/);
134
- expect(results[0].suggestedName).toMatch(/application/);
135
- // Should not include the irrelevant folder name "no"
136
- expect(results[0].suggestedName).not.toContain('no');
137
- });
138
-
139
- it('should extract name from employment contract', async () => {
140
- const fileInfo: FileInfo = {
141
- name: 'contract.pdf',
142
- path: '/temp/contract.pdf',
143
- extension: '.pdf',
144
- size: 1024 * 75,
145
- createdAt: new Date('2024-01-15'),
146
- modifiedAt: new Date('2024-02-01'),
147
- parentFolder: 'temp', // Should be ignored
148
- folderPath: ['home', 'temp']
149
- };
150
-
151
- const mockParser = {
152
- parse: vi.fn().mockResolvedValue({
153
- content: 'Employment contract between TechCorp Inc. and John Smith for the position of Senior Software Engineer. This contract outlines the terms of employment for John Smith including salary, benefits, and responsibilities.',
154
- metadata: {
155
- title: 'Employment Agreement',
156
- author: 'HR Department'
157
- }
158
- })
159
- };
160
-
161
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
162
-
163
- const results = await fileRenamer.renameFiles([fileInfo]);
164
-
165
- expect(results[0].success).toBe(true);
166
- // Should include employment/contract terms and person name extraction logic
167
- expect(results[0].suggestedName).toMatch(/employment|contract/);
168
- expect(results[0].suggestedName).not.toContain('temp');
169
- });
170
-
171
- it('should extract name from medical records', async () => {
172
- const fileInfo: FileInfo = {
173
- name: 'medical-file.pdf',
174
- path: '/misc/medical-file.pdf',
175
- extension: '.pdf',
176
- size: 1024 * 50,
177
- createdAt: new Date('2024-01-15'),
178
- modifiedAt: new Date('2024-02-01'),
179
- parentFolder: 'misc', // Should be ignored
180
- folderPath: ['home', 'misc']
181
- };
182
-
183
- const mockParser = {
184
- parse: vi.fn().mockResolvedValue({
185
- content: 'Medical record for Maria Rodriguez showing results of annual health checkup. Patient Maria Rodriguez underwent comprehensive examination including blood tests and physical assessment.',
186
- metadata: {
187
- title: 'Medical Record',
188
- pages: 3
189
- }
190
- })
191
- };
192
-
193
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
194
-
195
- const results = await fileRenamer.renameFiles([fileInfo]);
196
-
197
- expect(results[0].success).toBe(true);
198
- // Should start with detected person name and include medical terms
199
- expect(results[0].suggestedName).toMatch(/^maria-rodriguez/);
200
- expect(results[0].suggestedName).toMatch(/medical/);
201
- expect(results[0].suggestedName).not.toContain('misc');
202
- });
203
-
204
- it('should extract name from certificate documents', async () => {
205
- const fileInfo: FileInfo = {
206
- name: 'cert.pdf',
207
- path: '/downloads/cert.pdf',
208
- extension: '.pdf',
209
- size: 1024 * 25,
210
- createdAt: new Date('2024-01-15'),
211
- modifiedAt: new Date('2024-02-01'),
212
- parentFolder: 'downloads', // Should be ignored
213
- folderPath: ['home', 'downloads']
214
- };
215
-
216
- const mockParser = {
217
- parse: vi.fn().mockResolvedValue({
218
- content: 'Certificate of completion awarded to David Johnson for successfully completing the Advanced Web Development course. This certificate is issued to David Johnson in recognition of academic achievement.',
219
- metadata: {
220
- title: 'Certificate of Completion',
221
- pages: 1
222
- }
223
- })
224
- };
225
-
226
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
227
-
228
- const results = await fileRenamer.renameFiles([fileInfo]);
229
-
230
- expect(results[0].success).toBe(true);
231
- // Should include certificate/completion terms
232
- expect(results[0].suggestedName).toMatch(/certificate|completion/);
233
- expect(results[0].suggestedName).not.toContain('downloads');
234
- });
235
-
236
- it('should handle multiple names and pick the primary one', async () => {
237
- const fileInfo: FileInfo = {
238
- name: 'wedding-invite.pdf',
239
- path: '/other/wedding-invite.pdf',
240
- extension: '.pdf',
241
- size: 1024 * 30,
242
- createdAt: new Date('2024-01-15'),
243
- modifiedAt: new Date('2024-02-01'),
244
- parentFolder: 'other', // Should be ignored
245
- folderPath: ['home', 'other']
246
- };
247
-
248
- const mockParser = {
249
- parse: vi.fn().mockResolvedValue({
250
- content: 'Wedding ceremony invitation for Sarah Williams and Michael Brown. You are invited to celebrate the marriage of Sarah Williams and Michael Brown on June 15th, 2024.',
251
- metadata: {
252
- title: 'Wedding Invitation',
253
- pages: 1
254
- }
255
- })
256
- };
257
-
258
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
259
-
260
- const results = await fileRenamer.renameFiles([fileInfo]);
261
-
262
- expect(results[0].success).toBe(true);
263
- // Should include wedding terms and pick a name
264
- expect(results[0].suggestedName).toMatch(/sarah-williams/);
265
- expect(results[0].suggestedName).toMatch(/wedding/);
266
- expect(results[0].suggestedName).not.toContain('other');
267
- });
268
- });
269
-
270
- describe('Folder Name Filtering', () => {
271
- it('should ignore common irrelevant folder names', async () => {
272
- const irrelevantFolders = ['no', 'temp', 'downloads', 'misc', 'other', 'files'];
273
-
274
- for (const folderName of irrelevantFolders) {
275
- const fileInfo: FileInfo = {
276
- name: 'document.pdf',
277
- path: `/${folderName}/document.pdf`,
278
- extension: '.pdf',
279
- size: 1024 * 40,
280
- createdAt: new Date('2024-01-15'),
281
- modifiedAt: new Date('2024-02-01'),
282
- parentFolder: folderName,
283
- folderPath: ['home', folderName]
284
- };
285
-
286
- const mockParser = {
287
- parse: vi.fn().mockResolvedValue({
288
- content: 'Document summary report for quarterly business analysis and strategic planning initiatives.',
289
- metadata: {
290
- title: 'Business Report',
291
- pages: 10
292
- }
293
- })
294
- };
295
-
296
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
297
-
298
- const results = await fileRenamer.renameFiles([fileInfo]);
299
-
300
- expect(results[0].success).toBe(true);
301
- expect(results[0].suggestedName).not.toContain(folderName);
302
- expect(results[0].suggestedName).toMatch(/document.*summary.*report/);
303
- }
304
- });
305
-
306
- it('should handle meaningful folder names without excluding them entirely', async () => {
307
- const fileInfo: FileInfo = {
308
- name: 'report.pdf',
309
- path: '/financial-reports/report.pdf',
310
- extension: '.pdf',
311
- size: 1024 * 60,
312
- createdAt: new Date('2024-01-15'),
313
- modifiedAt: new Date('2024-02-01'),
314
- parentFolder: 'financial-reports', // This is meaningful
315
- folderPath: ['home', 'business', 'financial-reports']
316
- };
317
-
318
- const mockParser = {
319
- parse: vi.fn().mockResolvedValue({
320
- content: 'Annual financial summary report covering revenue, expenses, and profit analysis for fiscal year 2023.',
321
- metadata: {
322
- title: 'Financial Summary',
323
- pages: 15
324
- }
325
- })
326
- };
327
-
328
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
329
-
330
- const results = await fileRenamer.renameFiles([fileInfo]);
331
-
332
- expect(results[0].success).toBe(true);
333
- // Should focus on content, not necessarily include folder name
334
- expect(results[0].suggestedName).toMatch(/document.*summary.*report/);
335
- // But the folder context should still be available to the AI
336
- expect(fileInfo.parentFolder).toBe('financial-reports');
337
- });
338
- });
339
-
340
- describe('Edge Cases and Error Handling', () => {
341
- it('should handle documents without clear person names', async () => {
342
- const fileInfo: FileInfo = {
343
- name: 'generic.pdf',
344
- path: '/no/generic.pdf',
345
- extension: '.pdf',
346
- size: 1024 * 20,
347
- createdAt: new Date('2024-01-15'),
348
- modifiedAt: new Date('2024-02-01'),
349
- parentFolder: 'no',
350
- folderPath: ['home', 'no']
351
- };
352
-
353
- const mockParser = {
354
- parse: vi.fn().mockResolvedValue({
355
- content: 'This is a general business document containing policy information and guidelines for company operations.',
356
- metadata: {
357
- title: 'Policy Document',
358
- pages: 5
359
- }
360
- })
361
- };
362
-
363
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
364
-
365
- const results = await fileRenamer.renameFiles([fileInfo]);
366
-
367
- expect(results[0].success).toBe(true);
368
- expect(results[0].suggestedName).toMatch(/document.*summary.*report/);
369
- });
370
-
371
- it('should handle person names with special characters or formatting', async () => {
372
- const fileInfo: FileInfo = {
373
- name: 'contract.pdf',
374
- path: '/temp/contract.pdf',
375
- extension: '.pdf',
376
- size: 1024 * 45,
377
- createdAt: new Date('2024-01-15'),
378
- modifiedAt: new Date('2024-02-01'),
379
- parentFolder: 'temp',
380
- folderPath: ['home', 'temp']
381
- };
382
-
383
- const mockParser = {
384
- parse: vi.fn().mockResolvedValue({
385
- content: 'Employment agreement between Corporation XYZ and María José García-López for the position of Senior Data Scientist.',
386
- metadata: {
387
- title: 'Employment Contract',
388
- pages: 6
389
- }
390
- })
391
- };
392
-
393
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
394
-
395
- const results = await fileRenamer.renameFiles([fileInfo]);
396
-
397
- expect(results[0].success).toBe(true);
398
- // Should include employment/contract terms
399
- expect(results[0].suggestedName).toMatch(/employment|contract/);
400
- });
401
- });
402
-
403
- describe('Real-world Scenario Tests', () => {
404
- it('should handle the exact Setareh visa case from user example', async () => {
405
- const fileInfo: FileInfo = {
406
- name: 'visitor-visa-application-for-family-in-canada.pdf',
407
- path: '/#NO/visitor-visa-application-for-family-in-canada.pdf',
408
- extension: '.pdf',
409
- size: 1024 * 150,
410
- createdAt: new Date('2024-01-15'),
411
- modifiedAt: new Date('2024-02-01'),
412
- parentFolder: '#NO', // Note: even with special characters
413
- folderPath: ['home', 'documents', '#NO']
414
- };
415
-
416
- const mockParser = {
417
- parse: vi.fn().mockResolvedValue({
418
- content: 'Application for Visitor Visa (Temporary Resident Visa) for Setareh and family members to visit Canada. Applicant: Setareh [Last Name]. Purpose: Tourism and family visit. Duration: 3 weeks.',
419
- metadata: {
420
- title: 'Visitor Visa Application - TRV',
421
- author: 'Immigration Canada',
422
- pages: 6
423
- }
424
- })
425
- };
426
-
427
- vi.spyOn(fileRenamer['parserFactory'], 'getParser').mockReturnValue(mockParser);
428
-
429
- const results = await fileRenamer.renameFiles([fileInfo]);
430
-
431
- expect(results[0].success).toBe(true);
432
- // Should start with detected person name and include visa terms
433
- expect(results[0].suggestedName).toMatch(/^setareh/);
434
- expect(results[0].suggestedName).toMatch(/visa/);
435
- // Should NOT include "no" or "#NO"
436
- expect(results[0].suggestedName).not.toContain('no');
437
- expect(results[0].suggestedName).not.toContain('#no');
438
- });
439
- });
440
- });