sinapse-ai 1.9.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.claude/rules/mandatory-delegation.md +1 -1
  2. package/.codex/delegation-matrix.json +4 -3
  3. package/.codex/delegation-parity.json +4 -3
  4. package/.codex/instructions.md +2 -2
  5. package/.sinapse-ai/constitution.md +2 -2
  6. package/.sinapse-ai/core/doctor/checks/git-hooks.js +76 -10
  7. package/.sinapse-ai/core/execution/subagent-dispatcher.js +1 -1
  8. package/.sinapse-ai/core/synapse/engine.js +15 -0
  9. package/.sinapse-ai/data/entity-registry.yaml +13 -13
  10. package/.sinapse-ai/development/agents/snps-orqx.md +4 -4
  11. package/.sinapse-ai/git-hooks/lib/secret-scanner-core.js +76 -4
  12. package/.sinapse-ai/git-hooks/pre-push +7 -1
  13. package/.sinapse-ai/install-manifest.yaml +9 -9
  14. package/AGENTS.md +2 -2
  15. package/CHANGELOG.md +1247 -0
  16. package/bin/commands/uninstall.js +2 -2
  17. package/bin/utils/secret-scanner-core.js +76 -4
  18. package/docs/agent-reference-guide.md +1 -1
  19. package/docs/framework/architecture-overview.md +4 -4
  20. package/docs/framework/guiding-principles.md +9 -9
  21. package/docs/getting-started.md +1 -1
  22. package/docs/guides/agent-reference.md +1 -1
  23. package/docs/guides/codex-config.md +4 -5
  24. package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
  25. package/package.json +8 -2
  26. package/packages/installer/src/installer/git-hooks-installer.js +3 -1
  27. package/packages/installer/src/wizard/ide-config-generator.js +9 -1
  28. package/packages/installer/src/wizard/index.js +3 -4
  29. package/scripts/regenerate-orqx-stubs.ps1 +0 -1
  30. package/scripts/sync-counts.js +10 -2
  31. package/scripts/sync-squad-yaml-components.js +108 -6
  32. package/scripts/validate-squad-orqx.js +19 -9
  33. package/sinapse/agents/sinapse-orqx.md +4 -4
  34. package/sinapse/agents/snps-orqx.md +4 -4
  35. package/sinapse/knowledge-base/routing-catalog.md +1 -1
  36. package/sinapse/tasks/diagnose-and-route.md +1 -1
  37. package/sinapse/tasks/squad-status-report.md +1 -1
  38. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
  39. package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
  40. package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
  41. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  42. package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
  43. package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
  44. package/squads/squad-animations/README.md +1 -1
  45. package/squads/squad-cloning/README.md +1 -1
  46. package/squads/squad-commercial/README.md +1 -1
  47. package/squads/squad-content/README.md +1 -1
  48. package/squads/squad-copy/README.md +1 -1
  49. package/squads/squad-council/README.md +1 -1
  50. package/squads/squad-courses/README.md +1 -1
  51. package/squads/squad-cybersecurity/README.md +1 -1
  52. package/squads/squad-design/README.md +1 -1
  53. package/squads/squad-finance/README.md +1 -1
  54. package/squads/squad-growth/README.md +1 -1
  55. package/squads/squad-paidmedia/README.md +1 -1
  56. package/squads/squad-product/README.md +1 -1
  57. package/squads/squad-research/README.md +1 -1
  58. package/squads/squad-storytelling/README.md +1 -1
  59. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
  60. package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
  61. package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
  62. package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
  63. package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
  64. package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
  65. package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
  66. package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
  67. package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
  68. package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
  69. package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
  70. package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
  71. package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
  72. package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
  73. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
  74. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
  75. package/packages/installer/tests/unit/config-validator.test.js +0 -315
  76. package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
  77. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
  78. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
  79. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
  80. package/packages/installer/tests/unit/env-template.test.js +0 -187
  81. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
  82. package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
  83. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
  84. package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
  85. package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
  86. package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
  87. package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
  88. package/packages/sinapse-install/tests/unit/chrome-brain.smoke.test.js +0 -66
@@ -1,539 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { detectProjectType, detectProjectTypeExtended } = require('../../../src/detection/detect-project-type');
4
-
5
- // Mock fs module
6
- jest.mock('fs');
7
-
8
- describe('detectProjectType', () => {
9
- beforeEach(() => {
10
- // Clear all mocks before each test
11
- jest.clearAllMocks();
12
-
13
- // Reset console.error mock to avoid test pollution
14
- jest.spyOn(console, 'error').mockImplementation(() => {});
15
- });
16
-
17
- afterEach(() => {
18
- // Restore console.error
19
- console.error.mockRestore();
20
- });
21
-
22
- // ============================================================================
23
- // AC #1: Empty Directory → GREENFIELD
24
- // ============================================================================
25
- describe('AC #1: GREENFIELD detection', () => {
26
- test('detects GREENFIELD when directory is empty', () => {
27
- // Setup: Directory exists but has no files
28
- fs.existsSync.mockImplementation((checkPath) => {
29
- // Directory exists, but no markers
30
- if (checkPath === path.resolve('/test/empty')) return true;
31
- return false; // No .sinapse-ai, package.json, or .git
32
- });
33
- fs.readdirSync.mockReturnValue([]); // Empty directory
34
-
35
- const result = detectProjectType('/test/empty');
36
-
37
- expect(result).toBe('GREENFIELD');
38
- expect(fs.existsSync).toHaveBeenCalledWith(expect.stringContaining('.sinapse-ai'));
39
- expect(fs.readdirSync).toHaveBeenCalledTimes(1);
40
- });
41
-
42
- test('uses process.cwd() as default when no targetDir provided', () => {
43
- // Setup
44
- const mockCwd = process.cwd();
45
- fs.existsSync.mockImplementation((checkPath) => {
46
- if (checkPath === path.resolve(mockCwd)) return true;
47
- return false;
48
- });
49
- fs.readdirSync.mockReturnValue([]);
50
-
51
- const result = detectProjectType();
52
-
53
- expect(result).toBe('GREENFIELD');
54
- expect(fs.readdirSync).toHaveBeenCalledWith(path.resolve(mockCwd));
55
- });
56
- });
57
-
58
- // ============================================================================
59
- // AC #2: package.json Exists → BROWNFIELD
60
- // ============================================================================
61
- describe('AC #2: BROWNFIELD detection - package.json', () => {
62
- test('detects BROWNFIELD when package.json exists', () => {
63
- // Setup
64
- fs.existsSync.mockImplementation((checkPath) => {
65
- if (checkPath === path.resolve('/test/brownfield')) return true;
66
- if (checkPath.endsWith('package.json')) return true; // package.json exists
67
- return false; // No .sinapse-ai or .git
68
- });
69
- fs.readdirSync.mockReturnValue(['package.json', 'src', 'README.md']);
70
-
71
- const result = detectProjectType('/test/brownfield');
72
-
73
- expect(result).toBe('BROWNFIELD');
74
- });
75
-
76
- test('detects BROWNFIELD when only package.json exists (no git)', () => {
77
- // Setup: package.json but no .git
78
- fs.existsSync.mockImplementation((checkPath) => {
79
- if (checkPath === path.resolve('/test/node-project')) return true;
80
- if (checkPath.endsWith('package.json')) return true;
81
- if (checkPath.endsWith('.git')) return false;
82
- if (checkPath.endsWith('.sinapse-ai')) return false;
83
- return false;
84
- });
85
- fs.readdirSync.mockReturnValue(['package.json', 'index.js']);
86
-
87
- const result = detectProjectType('/test/node-project');
88
-
89
- expect(result).toBe('BROWNFIELD');
90
- });
91
- });
92
-
93
- // ============================================================================
94
- // AC #3: .git Exists → BROWNFIELD
95
- // ============================================================================
96
- describe('AC #3: BROWNFIELD detection - .git', () => {
97
- test('detects BROWNFIELD when .git exists', () => {
98
- // Setup
99
- fs.existsSync.mockImplementation((checkPath) => {
100
- if (checkPath === path.resolve('/test/brownfield-git')) return true;
101
- if (checkPath.endsWith('.git')) return true; // .git exists
102
- return false; // No .sinapse-ai or package.json
103
- });
104
- fs.readdirSync.mockReturnValue(['.git', 'README.md', 'src']);
105
-
106
- const result = detectProjectType('/test/brownfield-git');
107
-
108
- expect(result).toBe('BROWNFIELD');
109
- });
110
-
111
- test('detects BROWNFIELD when both package.json and .git exist', () => {
112
- // Setup: Both markers present
113
- fs.existsSync.mockImplementation((checkPath) => {
114
- if (checkPath === path.resolve('/test/full-project')) return true;
115
- if (checkPath.endsWith('package.json')) return true;
116
- if (checkPath.endsWith('.git')) return true;
117
- if (checkPath.endsWith('.sinapse-ai')) return false;
118
- return false;
119
- });
120
- fs.readdirSync.mockReturnValue(['.git', 'package.json', 'src', 'README.md']);
121
-
122
- const result = detectProjectType('/test/full-project');
123
-
124
- expect(result).toBe('BROWNFIELD');
125
- });
126
- });
127
-
128
- // ============================================================================
129
- // AC #4: .sinapse-ai Exists → EXISTING_SINAPSE
130
- // ============================================================================
131
- describe('AC #4: EXISTING_SINAPSE detection', () => {
132
- test('detects EXISTING_SINAPSE when .sinapse-ai exists', () => {
133
- // Setup
134
- fs.existsSync.mockImplementation((checkPath) => {
135
- if (checkPath === path.resolve('/test/existing')) return true;
136
- if (checkPath.endsWith('.sinapse-ai')) return true; // .sinapse-ai exists
137
- return true; // Other files may exist too
138
- });
139
- fs.readdirSync.mockReturnValue(['.sinapse-ai', 'package.json', '.git', 'src']);
140
-
141
- const result = detectProjectType('/test/existing');
142
-
143
- expect(result).toBe('EXISTING_SINAPSE');
144
- });
145
-
146
- test('EXISTING_SINAPSE takes priority over BROWNFIELD markers', () => {
147
- // Setup: All markers present, but EXISTING_SINAPSE should win
148
- fs.existsSync.mockImplementation((checkPath) => {
149
- if (checkPath === path.resolve('/test/priority-test')) return true;
150
- // All markers present
151
- if (checkPath.endsWith('.sinapse-ai')) return true;
152
- if (checkPath.endsWith('package.json')) return true;
153
- if (checkPath.endsWith('.git')) return true;
154
- return false;
155
- });
156
- fs.readdirSync.mockReturnValue(['.sinapse-ai', 'package.json', '.git', 'src']);
157
-
158
- const result = detectProjectType('/test/priority-test');
159
-
160
- // EXISTING_SINAPSE has highest priority
161
- expect(result).toBe('EXISTING_SINAPSE');
162
- });
163
- });
164
-
165
- // ============================================================================
166
- // Edge Case: UNKNOWN Type
167
- // ============================================================================
168
- describe('Edge Case: UNKNOWN detection', () => {
169
- test('returns UNKNOWN when directory has files but no recognized markers', () => {
170
- // Setup: Files exist but no package.json, .git, or .sinapse-ai
171
- fs.existsSync.mockImplementation((checkPath) => {
172
- if (checkPath === path.resolve('/test/unknown')) return true;
173
- // No recognized markers
174
- return false;
175
- });
176
- fs.readdirSync.mockReturnValue(['README.md', 'index.html', 'styles.css']);
177
-
178
- const result = detectProjectType('/test/unknown');
179
-
180
- expect(result).toBe('UNKNOWN');
181
- });
182
-
183
- test('returns UNKNOWN for non-empty directory with only hidden files', () => {
184
- // Setup
185
- fs.existsSync.mockImplementation((checkPath) => {
186
- if (checkPath === path.resolve('/test/hidden-only')) return true;
187
- return false; // No recognized markers
188
- });
189
- fs.readdirSync.mockReturnValue(['.DS_Store', '.gitignore']); // Hidden files only
190
-
191
- const result = detectProjectType('/test/hidden-only');
192
-
193
- expect(result).toBe('UNKNOWN');
194
- });
195
- });
196
-
197
- // ============================================================================
198
- // Error Handling Tests
199
- // ============================================================================
200
- describe('Error Handling', () => {
201
- test('throws error when directory does not exist', () => {
202
- // Setup: Directory doesn't exist
203
- fs.existsSync.mockImplementation((checkPath) => {
204
- if (checkPath === path.resolve('/test/nonexistent')) return false;
205
- return false;
206
- });
207
-
208
- expect(() => detectProjectType('/test/nonexistent')).toThrow('Failed to detect project type');
209
- expect(() => detectProjectType('/test/nonexistent')).toThrow('Directory does not exist');
210
- });
211
-
212
- test('throws error when directory cannot be accessed (EACCES)', () => {
213
- // Setup: Permission denied error
214
- const permissionError = new Error('EACCES: permission denied');
215
- permissionError.code = 'EACCES';
216
-
217
- fs.existsSync.mockImplementation((checkPath) => {
218
- if (checkPath === path.resolve('/test/denied')) return true;
219
- if (checkPath.endsWith('.sinapse-ai')) {
220
- throw permissionError;
221
- }
222
- return false;
223
- });
224
-
225
- expect(() => detectProjectType('/test/denied')).toThrow('Failed to detect project type');
226
- expect(console.error).toHaveBeenCalledWith(
227
- expect.stringContaining('[detect-project-type] Error detecting project type'),
228
- );
229
- });
230
-
231
- test('throws error when fs.readdirSync fails', () => {
232
- // Setup: readdirSync throws error
233
- const readError = new Error('EPERM: operation not permitted');
234
- readError.code = 'EPERM';
235
-
236
- fs.existsSync.mockImplementation((checkPath) => {
237
- if (checkPath === path.resolve('/test/read-error')) return true;
238
- return false;
239
- });
240
- fs.readdirSync.mockImplementation(() => {
241
- throw readError;
242
- });
243
-
244
- expect(() => detectProjectType('/test/read-error')).toThrow('Failed to detect project type');
245
- expect(console.error).toHaveBeenCalled();
246
- });
247
-
248
- test('throws error with invalid targetDir parameter (null)', () => {
249
- expect(() => detectProjectType(null)).toThrow('Invalid targetDir parameter');
250
- });
251
-
252
- test('throws error with invalid targetDir parameter (empty string)', () => {
253
- expect(() => detectProjectType('')).toThrow('Invalid targetDir parameter');
254
- });
255
-
256
- test('throws error with invalid targetDir parameter (number)', () => {
257
- expect(() => detectProjectType(123)).toThrow('Invalid targetDir parameter');
258
- });
259
- });
260
-
261
- // ============================================================================
262
- // Priority Order Verification
263
- // ============================================================================
264
- describe('Detection Priority Order', () => {
265
- test('priority: EXISTING_SINAPSE > GREENFIELD', () => {
266
- // Even if directory appears empty, .sinapse-ai presence should win
267
- fs.existsSync.mockImplementation((checkPath) => {
268
- if (checkPath === path.resolve('/test/priority1')) return true;
269
- if (checkPath.endsWith('.sinapse-ai')) return true;
270
- return false;
271
- });
272
- fs.readdirSync.mockReturnValue(['.sinapse-ai']); // Only .sinapse-ai
273
-
274
- const result = detectProjectType('/test/priority1');
275
-
276
- expect(result).toBe('EXISTING_SINAPSE');
277
- });
278
-
279
- test('priority: GREENFIELD > BROWNFIELD when directory is empty', () => {
280
- // This test verifies the order, though practically impossible scenario
281
- fs.existsSync.mockImplementation((checkPath) => {
282
- if (checkPath === path.resolve('/test/priority2')) return true;
283
- return false; // No markers
284
- });
285
- fs.readdirSync.mockReturnValue([]); // Empty
286
-
287
- const result = detectProjectType('/test/priority2');
288
-
289
- expect(result).toBe('GREENFIELD');
290
- });
291
-
292
- test('priority: BROWNFIELD > UNKNOWN', () => {
293
- // Directory has files + package.json
294
- fs.existsSync.mockImplementation((checkPath) => {
295
- if (checkPath === path.resolve('/test/priority3')) return true;
296
- if (checkPath.endsWith('package.json')) return true;
297
- if (checkPath.endsWith('.sinapse-ai')) return false;
298
- if (checkPath.endsWith('.git')) return false;
299
- return false;
300
- });
301
- fs.readdirSync.mockReturnValue(['package.json', 'other-file.txt']);
302
-
303
- const result = detectProjectType('/test/priority3');
304
-
305
- expect(result).toBe('BROWNFIELD');
306
- });
307
- });
308
-
309
- // ============================================================================
310
- // Cross-Platform Path Handling
311
- // ============================================================================
312
- describe('Cross-Platform Compatibility', () => {
313
- test('handles Windows-style paths correctly', () => {
314
- const windowsPath = 'C:\\Users\\Test\\project';
315
-
316
- fs.existsSync.mockImplementation((checkPath) => {
317
- if (checkPath === path.resolve(windowsPath)) return true;
318
- return false;
319
- });
320
- fs.readdirSync.mockReturnValue([]);
321
-
322
- const result = detectProjectType(windowsPath);
323
-
324
- expect(result).toBe('GREENFIELD');
325
- expect(fs.existsSync).toHaveBeenCalledWith(expect.any(String));
326
- });
327
-
328
- test('handles Unix-style paths correctly', () => {
329
- const unixPath = '/home/user/project';
330
-
331
- fs.existsSync.mockImplementation((checkPath) => {
332
- if (checkPath === path.resolve(unixPath)) return true;
333
- return false;
334
- });
335
- fs.readdirSync.mockReturnValue([]);
336
-
337
- const result = detectProjectType(unixPath);
338
-
339
- expect(result).toBe('GREENFIELD');
340
- });
341
-
342
- test('normalizes relative paths correctly', () => {
343
- const relativePath = './test/project';
344
-
345
- fs.existsSync.mockImplementation((checkPath) => {
346
- if (checkPath === path.resolve(relativePath)) return true;
347
- return false;
348
- });
349
- fs.readdirSync.mockReturnValue([]);
350
-
351
- const result = detectProjectType(relativePath);
352
-
353
- expect(result).toBe('GREENFIELD');
354
- });
355
- });
356
-
357
- // ============================================================================
358
- // Security Tests
359
- // ============================================================================
360
- describe('Security - Path Traversal Prevention', () => {
361
- test('uses path.join for all file checks (prevents traversal)', () => {
362
- fs.existsSync.mockImplementation((checkPath) => {
363
- if (checkPath === path.resolve('/test/secure')) return true;
364
- // Verify path.join was used (no string concatenation)
365
- expect(checkPath).toContain(path.sep);
366
- return false;
367
- });
368
- fs.readdirSync.mockReturnValue([]);
369
-
370
- detectProjectType('/test/secure');
371
-
372
- // Verify all existsSync calls use proper path joining
373
- expect(fs.existsSync).toHaveBeenCalledWith(expect.stringContaining(path.sep));
374
- });
375
-
376
- test('normalizes path to prevent directory traversal attacks', () => {
377
- const maliciousPath = '/test/../../etc/passwd';
378
- const resolvedPath = path.resolve(maliciousPath);
379
-
380
- fs.existsSync.mockImplementation((checkPath) => {
381
- // Directory exists
382
- if (checkPath === resolvedPath) return true;
383
- // Verify all paths contain the normalized base path
384
- if (checkPath.includes(resolvedPath)) return false;
385
- return false;
386
- });
387
- fs.readdirSync.mockReturnValue([]);
388
-
389
- const result = detectProjectType(maliciousPath);
390
-
391
- expect(result).toBe('GREENFIELD');
392
- expect(fs.existsSync).toHaveBeenCalled();
393
- // Verify the normalized path was used as base
394
- const calls = fs.existsSync.mock.calls;
395
- calls.forEach(([callPath]) => {
396
- expect(callPath).toContain(path.normalize(resolvedPath).split(path.sep)[0]);
397
- });
398
- });
399
- });
400
- });
401
-
402
- // ============================================================================
403
- // detectProjectTypeExtended
404
- // ============================================================================
405
- describe('detectProjectTypeExtended', () => {
406
- beforeEach(() => {
407
- jest.clearAllMocks();
408
- jest.spyOn(console, 'error').mockImplementation(() => {});
409
- });
410
-
411
- afterEach(() => {
412
- console.error.mockRestore();
413
- });
414
-
415
- test('returns object with type, techStack, maturityScore, recommendations', () => {
416
- fs.existsSync.mockImplementation((checkPath) => {
417
- if (checkPath === path.resolve('/test/ext')) return true;
418
- return false;
419
- });
420
- fs.readdirSync.mockReturnValue([]);
421
-
422
- const result = detectProjectTypeExtended('/test/ext');
423
-
424
- expect(result).toHaveProperty('type', 'GREENFIELD');
425
- expect(result).toHaveProperty('techStack');
426
- expect(result).toHaveProperty('maturityScore', 0);
427
- expect(result).toHaveProperty('recommendations');
428
- expect(Array.isArray(result.recommendations)).toBe(true);
429
- });
430
-
431
- test('GREENFIELD returns empty tech stack and score 0', () => {
432
- fs.existsSync.mockImplementation((checkPath) => {
433
- if (checkPath === path.resolve('/test/green')) return true;
434
- return false;
435
- });
436
- fs.readdirSync.mockReturnValue([]);
437
-
438
- const result = detectProjectTypeExtended('/test/green');
439
-
440
- expect(result.type).toBe('GREENFIELD');
441
- expect(result.techStack.framework).toBeNull();
442
- expect(result.techStack.language).toBeNull();
443
- expect(result.techStack.database).toBeNull();
444
- expect(result.techStack.testing).toBeNull();
445
- expect(result.techStack.ci).toBeNull();
446
- expect(result.maturityScore).toBe(0);
447
- });
448
-
449
- test('detects framework from package.json dependencies', () => {
450
- const dir = path.resolve('/test/next-project');
451
- fs.existsSync.mockImplementation((checkPath) => {
452
- if (checkPath === dir) return true;
453
- if (checkPath === path.join(dir, 'package.json')) return true;
454
- if (checkPath === path.join(dir, 'tsconfig.json')) return true;
455
- return false;
456
- });
457
- fs.readdirSync.mockImplementation((readDir) => {
458
- if (typeof readDir === 'string' && readDir === dir) return ['package.json', 'tsconfig.json', 'src'];
459
- return [];
460
- });
461
- fs.readFileSync.mockReturnValue(JSON.stringify({
462
- dependencies: { next: '14.0.0', react: '18.0.0' },
463
- devDependencies: { jest: '29.0.0' },
464
- }));
465
-
466
- const result = detectProjectTypeExtended('/test/next-project');
467
-
468
- expect(result.type).toBe('BROWNFIELD');
469
- expect(result.techStack.framework).toBe('next');
470
- expect(result.techStack.language).toBe('typescript');
471
- expect(result.techStack.testing).toBe('jest');
472
- });
473
-
474
- test('detects CI from .github/workflows', () => {
475
- const dir = path.resolve('/test/ci-project');
476
- fs.existsSync.mockImplementation((checkPath) => {
477
- if (checkPath === dir) return true;
478
- if (checkPath === path.join(dir, '.git')) return true;
479
- if (checkPath === path.join(dir, '.github', 'workflows')) return true;
480
- return false;
481
- });
482
- fs.readdirSync.mockImplementation((readDir) => {
483
- if (typeof readDir === 'string' && readDir === dir) return ['.git', '.github'];
484
- return [];
485
- });
486
-
487
- const result = detectProjectTypeExtended('/test/ci-project');
488
-
489
- expect(result.techStack.ci).toBe('github-actions');
490
- });
491
-
492
- test('builds recommendations for missing items', () => {
493
- const dir = path.resolve('/test/recs');
494
- fs.existsSync.mockImplementation((checkPath) => {
495
- if (checkPath === dir) return true;
496
- if (checkPath === path.join(dir, 'package.json')) return true;
497
- return false;
498
- });
499
- fs.readdirSync.mockImplementation((readDir) => {
500
- if (typeof readDir === 'string' && readDir === dir) return ['package.json'];
501
- return [];
502
- });
503
- fs.readFileSync.mockReturnValue(JSON.stringify({ dependencies: {} }));
504
-
505
- const result = detectProjectTypeExtended('/test/recs');
506
-
507
- expect(result.recommendations).toEqual(expect.arrayContaining([
508
- expect.stringContaining('testing framework'),
509
- expect.stringContaining('CI/CD'),
510
- expect.stringContaining('.env.example'),
511
- expect.stringContaining('README'),
512
- ]));
513
- });
514
-
515
- test('backward compat: detectProjectType returns same string as extended .type', () => {
516
- fs.existsSync.mockImplementation((checkPath) => {
517
- if (checkPath === path.resolve('/test/compat')) return true;
518
- if (checkPath.endsWith('package.json')) return true;
519
- return false;
520
- });
521
- fs.readdirSync.mockImplementation((readDir) => {
522
- if (typeof readDir === 'string') return ['package.json'];
523
- return [];
524
- });
525
- fs.readFileSync.mockReturnValue(JSON.stringify({ dependencies: {} }));
526
-
527
- const simple = detectProjectType('/test/compat');
528
- const extended = detectProjectTypeExtended('/test/compat');
529
-
530
- expect(simple).toBe(extended.type);
531
- expect(simple).toBe('BROWNFIELD');
532
- });
533
-
534
- test('throws same errors as original for invalid input', () => {
535
- expect(() => detectProjectTypeExtended(null)).toThrow('Invalid targetDir parameter');
536
- expect(() => detectProjectTypeExtended('')).toThrow('Invalid targetDir parameter');
537
- expect(() => detectProjectTypeExtended(123)).toThrow('Invalid targetDir parameter');
538
- });
539
- });