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.
- package/.claude/rules/mandatory-delegation.md +1 -1
- package/.codex/delegation-matrix.json +4 -3
- package/.codex/delegation-parity.json +4 -3
- package/.codex/instructions.md +2 -2
- package/.sinapse-ai/constitution.md +2 -2
- package/.sinapse-ai/core/doctor/checks/git-hooks.js +76 -10
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +1 -1
- package/.sinapse-ai/core/synapse/engine.js +15 -0
- package/.sinapse-ai/data/entity-registry.yaml +13 -13
- package/.sinapse-ai/development/agents/snps-orqx.md +4 -4
- package/.sinapse-ai/git-hooks/lib/secret-scanner-core.js +76 -4
- package/.sinapse-ai/git-hooks/pre-push +7 -1
- package/.sinapse-ai/install-manifest.yaml +9 -9
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +1247 -0
- package/bin/commands/uninstall.js +2 -2
- package/bin/utils/secret-scanner-core.js +76 -4
- package/docs/agent-reference-guide.md +1 -1
- package/docs/framework/architecture-overview.md +4 -4
- package/docs/framework/guiding-principles.md +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/guides/agent-reference.md +1 -1
- package/docs/guides/codex-config.md +4 -5
- package/docs/pt/architecture/sub-orqx-pattern.md +20 -18
- package/package.json +8 -2
- package/packages/installer/src/installer/git-hooks-installer.js +3 -1
- package/packages/installer/src/wizard/ide-config-generator.js +9 -1
- package/packages/installer/src/wizard/index.js +3 -4
- package/scripts/regenerate-orqx-stubs.ps1 +0 -1
- package/scripts/sync-counts.js +10 -2
- package/scripts/sync-squad-yaml-components.js +108 -6
- package/scripts/validate-squad-orqx.js +19 -9
- package/sinapse/agents/sinapse-orqx.md +4 -4
- package/sinapse/agents/snps-orqx.md +4 -4
- package/sinapse/knowledge-base/routing-catalog.md +1 -1
- package/sinapse/tasks/diagnose-and-route.md +1 -1
- package/sinapse/tasks/squad-status-report.md +1 -1
- package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
- package/squads/claude-code-mastery/agents/hooks-architect.md +60 -68
- package/squads/claude-code-mastery/knowledge-base/swarm-orchestration-patterns.md +1 -1
- package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
- package/squads/claude-code-mastery/workflows/optimization-cycle.yaml +4 -4
- package/squads/claude-code-mastery/workflows/project-setup-cycle.yaml +4 -4
- package/squads/squad-animations/README.md +1 -1
- package/squads/squad-cloning/README.md +1 -1
- package/squads/squad-commercial/README.md +1 -1
- package/squads/squad-content/README.md +1 -1
- package/squads/squad-copy/README.md +1 -1
- package/squads/squad-council/README.md +1 -1
- package/squads/squad-courses/README.md +1 -1
- package/squads/squad-cybersecurity/README.md +1 -1
- package/squads/squad-design/README.md +1 -1
- package/squads/squad-finance/README.md +1 -1
- package/squads/squad-growth/README.md +1 -1
- package/squads/squad-paidmedia/README.md +1 -1
- package/squads/squad-product/README.md +1 -1
- package/squads/squad-research/README.md +1 -1
- package/squads/squad-storytelling/README.md +1 -1
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +0 -265
- package/.sinapse-ai/core/permissions/__tests__/permission-mode.test.js +0 -293
- package/.sinapse-ai/infrastructure/tests/project-status-loader.test.js +0 -569
- package/.sinapse-ai/infrastructure/tests/regression-suite-v2.md +0 -622
- package/.sinapse-ai/infrastructure/tests/validate-module.js +0 -98
- package/.sinapse-ai/infrastructure/tests/worktree-manager.test.js +0 -620
- package/.sinapse-ai/workflow-intelligence/__tests__/confidence-scorer.test.js +0 -335
- package/.sinapse-ai/workflow-intelligence/__tests__/integration.test.js +0 -340
- package/.sinapse-ai/workflow-intelligence/__tests__/suggestion-engine.test.js +0 -438
- package/.sinapse-ai/workflow-intelligence/__tests__/wave-analyzer.test.js +0 -448
- package/.sinapse-ai/workflow-intelligence/__tests__/workflow-registry.test.js +0 -303
- package/packages/installer/src/__tests__/performance-benchmark.js +0 -383
- package/packages/installer/tests/integration/environment-configuration.test.js +0 -332
- package/packages/installer/tests/integration/wizard-detection.test.js +0 -352
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +0 -402
- package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +0 -193
- package/packages/installer/tests/unit/config-validator.test.js +0 -315
- package/packages/installer/tests/unit/detection/detect-project-type.test.js +0 -539
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +0 -675
- package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +0 -192
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +0 -192
- package/packages/installer/tests/unit/env-template.test.js +0 -187
- package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +0 -310
- package/packages/installer/tests/unit/git-hooks-installer.test.js +0 -262
- package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +0 -231
- package/packages/installer/tests/unit/merger/env-merger.test.js +0 -191
- package/packages/installer/tests/unit/merger/markdown-merger.test.js +0 -262
- package/packages/installer/tests/unit/merger/strategies.test.js +0 -154
- package/packages/installer/tests/unit/merger/yaml-merger.test.js +0 -328
- 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
|
-
});
|