sinapse-ai 1.9.0 → 1.10.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 (94) 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 +4 -4
  15. package/CHANGELOG.md +1257 -0
  16. package/README.en.md +4 -3
  17. package/README.md +5 -6
  18. package/bin/commands/install.js +6 -3
  19. package/bin/commands/uninstall.js +2 -2
  20. package/bin/sinapse.js +20 -0
  21. package/bin/utils/secret-scanner-core.js +76 -4
  22. package/docs/agent-reference-guide.md +1 -1
  23. package/docs/framework/architecture-overview.md +4 -4
  24. package/docs/framework/guiding-principles.md +9 -9
  25. package/docs/getting-started.md +1 -1
  26. package/docs/guides/agent-reference.md +1 -1
  27. package/docs/guides/codex-config.md +4 -5
  28. package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
  29. package/package.json +8 -2
  30. package/packages/installer/src/installer/git-hooks-installer.js +3 -1
  31. package/packages/installer/src/installer/sinapse-ai-installer.js +32 -7
  32. package/packages/installer/src/wizard/ide-config-generator.js +9 -1
  33. package/packages/installer/src/wizard/index.js +3 -4
  34. package/scripts/regenerate-orqx-stubs.ps1 +0 -1
  35. package/scripts/sync-counts.js +10 -2
  36. package/scripts/sync-squad-yaml-components.js +108 -6
  37. package/scripts/validate-article-vii.js +68 -7
  38. package/scripts/validate-squad-orqx.js +19 -9
  39. package/sinapse/agents/sinapse-orqx.md +4 -4
  40. package/sinapse/agents/snps-orqx.md +4 -4
  41. package/sinapse/knowledge-base/routing-catalog.md +1 -1
  42. package/sinapse/tasks/diagnose-and-route.md +1 -1
  43. package/sinapse/tasks/squad-status-report.md +1 -1
  44. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
  45. package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
  46. package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
  47. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  48. package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
  49. package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
  50. package/squads/squad-animations/README.md +1 -1
  51. package/squads/squad-cloning/README.md +1 -1
  52. package/squads/squad-commercial/README.md +1 -1
  53. package/squads/squad-content/README.md +1 -1
  54. package/squads/squad-copy/README.md +1 -1
  55. package/squads/squad-council/README.md +1 -1
  56. package/squads/squad-courses/README.md +1 -1
  57. package/squads/squad-cybersecurity/README.md +1 -1
  58. package/squads/squad-design/README.md +1 -1
  59. package/squads/squad-finance/README.md +1 -1
  60. package/squads/squad-growth/README.md +1 -1
  61. package/squads/squad-paidmedia/README.md +1 -1
  62. package/squads/squad-product/README.md +1 -1
  63. package/squads/squad-research/README.md +1 -1
  64. package/squads/squad-storytelling/README.md +1 -1
  65. package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
  66. package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
  67. package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
  68. package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
  69. package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
  70. package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
  71. package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
  72. package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
  73. package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
  74. package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
  75. package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
  76. package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
  77. package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
  78. package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
  79. package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
  80. package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
  81. package/packages/installer/tests/unit/config-validator.test.js +0 -315
  82. package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
  83. package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
  84. package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
  85. package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
  86. package/packages/installer/tests/unit/env-template.test.js +0 -187
  87. package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
  88. package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
  89. package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
  90. package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
  91. package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
  92. package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
  93. package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
  94. 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
- });