@skillsmith/mcp-server 0.1.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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/src/__tests__/get-skill.test.d.ts +6 -0
- package/dist/src/__tests__/get-skill.test.d.ts.map +1 -0
- package/dist/src/__tests__/get-skill.test.js +88 -0
- package/dist/src/__tests__/get-skill.test.js.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.d.ts +7 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.js +304 -0
- package/dist/src/__tests__/middleware/errorFormatter.test.js.map +1 -0
- package/dist/src/__tests__/middleware/license.test.d.ts +7 -0
- package/dist/src/__tests__/middleware/license.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/license.test.js +500 -0
- package/dist/src/__tests__/middleware/license.test.js.map +1 -0
- package/dist/src/__tests__/search.test.d.ts +6 -0
- package/dist/src/__tests__/search.test.d.ts.map +1 -0
- package/dist/src/__tests__/search.test.js +86 -0
- package/dist/src/__tests__/search.test.js.map +1 -0
- package/dist/src/__tests__/test-utils.d.ts +19 -0
- package/dist/src/__tests__/test-utils.d.ts.map +1 -0
- package/dist/src/__tests__/test-utils.js +87 -0
- package/dist/src/__tests__/test-utils.js.map +1 -0
- package/dist/src/context/index.d.ts +19 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +25 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/context/project-detector.d.ts +145 -0
- package/dist/src/context/project-detector.d.ts.map +1 -0
- package/dist/src/context/project-detector.js +321 -0
- package/dist/src/context/project-detector.js.map +1 -0
- package/dist/src/context.d.ts +100 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/src/context.js +157 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/core-shim.d.ts +7 -0
- package/dist/src/core-shim.d.ts.map +1 -0
- package/dist/src/core-shim.js +9 -0
- package/dist/src/core-shim.js.map +1 -0
- package/dist/src/health/healthCheck.d.ts +88 -0
- package/dist/src/health/healthCheck.d.ts.map +1 -0
- package/dist/src/health/healthCheck.js +117 -0
- package/dist/src/health/healthCheck.js.map +1 -0
- package/dist/src/health/index.d.ts +21 -0
- package/dist/src/health/index.d.ts.map +1 -0
- package/dist/src/health/index.js +21 -0
- package/dist/src/health/index.js.map +1 -0
- package/dist/src/health/readinessCheck.d.ts +139 -0
- package/dist/src/health/readinessCheck.d.ts.map +1 -0
- package/dist/src/health/readinessCheck.js +266 -0
- package/dist/src/health/readinessCheck.js.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +178 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/index.test.d.ts +2 -0
- package/dist/src/index.test.d.ts.map +1 -0
- package/dist/src/index.test.js +43 -0
- package/dist/src/index.test.js.map +1 -0
- package/dist/src/logger.d.ts +26 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +179 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/middleware/__tests__/csp.test.d.ts +2 -0
- package/dist/src/middleware/__tests__/csp.test.d.ts.map +1 -0
- package/dist/src/middleware/__tests__/csp.test.js +389 -0
- package/dist/src/middleware/__tests__/csp.test.js.map +1 -0
- package/dist/src/middleware/csp.d.ts +87 -0
- package/dist/src/middleware/csp.d.ts.map +1 -0
- package/dist/src/middleware/csp.js +273 -0
- package/dist/src/middleware/csp.js.map +1 -0
- package/dist/src/middleware/degradation.d.ts +99 -0
- package/dist/src/middleware/degradation.d.ts.map +1 -0
- package/dist/src/middleware/degradation.js +315 -0
- package/dist/src/middleware/degradation.js.map +1 -0
- package/dist/src/middleware/errorFormatter.d.ts +119 -0
- package/dist/src/middleware/errorFormatter.d.ts.map +1 -0
- package/dist/src/middleware/errorFormatter.js +294 -0
- package/dist/src/middleware/errorFormatter.js.map +1 -0
- package/dist/src/middleware/index.d.ts +10 -0
- package/dist/src/middleware/index.d.ts.map +1 -0
- package/dist/src/middleware/index.js +14 -0
- package/dist/src/middleware/index.js.map +1 -0
- package/dist/src/middleware/license.d.ts +161 -0
- package/dist/src/middleware/license.d.ts.map +1 -0
- package/dist/src/middleware/license.js +281 -0
- package/dist/src/middleware/license.js.map +1 -0
- package/dist/src/middleware/toolFeatureMapping.d.ts +36 -0
- package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -0
- package/dist/src/middleware/toolFeatureMapping.js +90 -0
- package/dist/src/middleware/toolFeatureMapping.js.map +1 -0
- package/dist/src/onboarding/first-run.d.ts +64 -0
- package/dist/src/onboarding/first-run.d.ts.map +1 -0
- package/dist/src/onboarding/first-run.js +77 -0
- package/dist/src/onboarding/first-run.js.map +1 -0
- package/dist/src/onboarding/index.d.ts +7 -0
- package/dist/src/onboarding/index.d.ts.map +1 -0
- package/dist/src/onboarding/index.js +7 -0
- package/dist/src/onboarding/index.js.map +1 -0
- package/dist/src/suggestions/index.d.ts +21 -0
- package/dist/src/suggestions/index.d.ts.map +1 -0
- package/dist/src/suggestions/index.js +20 -0
- package/dist/src/suggestions/index.js.map +1 -0
- package/dist/src/suggestions/suggestion-engine.d.ts +185 -0
- package/dist/src/suggestions/suggestion-engine.d.ts.map +1 -0
- package/dist/src/suggestions/suggestion-engine.js +352 -0
- package/dist/src/suggestions/suggestion-engine.js.map +1 -0
- package/dist/src/suggestions/types.d.ts +88 -0
- package/dist/src/suggestions/types.d.ts.map +1 -0
- package/dist/src/suggestions/types.js +21 -0
- package/dist/src/suggestions/types.js.map +1 -0
- package/dist/src/tools/analyze.d.ts +151 -0
- package/dist/src/tools/analyze.d.ts.map +1 -0
- package/dist/src/tools/analyze.js +205 -0
- package/dist/src/tools/analyze.js.map +1 -0
- package/dist/src/tools/compare.d.ts +149 -0
- package/dist/src/tools/compare.d.ts.map +1 -0
- package/dist/src/tools/compare.js +464 -0
- package/dist/src/tools/compare.js.map +1 -0
- package/dist/src/tools/get-skill.d.ts +116 -0
- package/dist/src/tools/get-skill.d.ts.map +1 -0
- package/dist/src/tools/get-skill.js +224 -0
- package/dist/src/tools/get-skill.js.map +1 -0
- package/dist/src/tools/index.d.ts +20 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +20 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/install.d.ts +122 -0
- package/dist/src/tools/install.d.ts.map +1 -0
- package/dist/src/tools/install.js +314 -0
- package/dist/src/tools/install.js.map +1 -0
- package/dist/src/tools/recommend.d.ts +171 -0
- package/dist/src/tools/recommend.d.ts.map +1 -0
- package/dist/src/tools/recommend.js +325 -0
- package/dist/src/tools/recommend.js.map +1 -0
- package/dist/src/tools/search.d.ts +121 -0
- package/dist/src/tools/search.d.ts.map +1 -0
- package/dist/src/tools/search.js +249 -0
- package/dist/src/tools/search.js.map +1 -0
- package/dist/src/tools/suggest.d.ts +181 -0
- package/dist/src/tools/suggest.d.ts.map +1 -0
- package/dist/src/tools/suggest.js +342 -0
- package/dist/src/tools/suggest.js.map +1 -0
- package/dist/src/tools/uninstall.d.ts +123 -0
- package/dist/src/tools/uninstall.d.ts.map +1 -0
- package/dist/src/tools/uninstall.js +250 -0
- package/dist/src/tools/uninstall.js.map +1 -0
- package/dist/src/tools/validate.d.ts +122 -0
- package/dist/src/tools/validate.d.ts.map +1 -0
- package/dist/src/tools/validate.js +497 -0
- package/dist/src/tools/validate.js.map +1 -0
- package/dist/src/utils/installed-skills.d.ts +101 -0
- package/dist/src/utils/installed-skills.d.ts.map +1 -0
- package/dist/src/utils/installed-skills.js +220 -0
- package/dist/src/utils/installed-skills.js.map +1 -0
- package/dist/src/utils/validation.d.ts +76 -0
- package/dist/src/utils/validation.d.ts.map +1 -0
- package/dist/src/utils/validation.js +153 -0
- package/dist/src/utils/validation.js.map +1 -0
- package/dist/src/webhooks/index.d.ts +8 -0
- package/dist/src/webhooks/index.d.ts.map +1 -0
- package/dist/src/webhooks/index.js +9 -0
- package/dist/src/webhooks/index.js.map +1 -0
- package/dist/src/webhooks/webhook-endpoint.d.ts +149 -0
- package/dist/src/webhooks/webhook-endpoint.d.ts.map +1 -0
- package/dist/src/webhooks/webhook-endpoint.js +339 -0
- package/dist/src/webhooks/webhook-endpoint.js.map +1 -0
- package/dist/tests/compare.test.d.ts +6 -0
- package/dist/tests/compare.test.d.ts.map +1 -0
- package/dist/tests/compare.test.js +225 -0
- package/dist/tests/compare.test.js.map +1 -0
- package/dist/tests/context/project-detector.test.d.ts +6 -0
- package/dist/tests/context/project-detector.test.d.ts.map +1 -0
- package/dist/tests/context/project-detector.test.js +719 -0
- package/dist/tests/context/project-detector.test.js.map +1 -0
- package/dist/tests/e2e/compare.e2e.test.d.ts +10 -0
- package/dist/tests/e2e/compare.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/compare.e2e.test.js +286 -0
- package/dist/tests/e2e/compare.e2e.test.js.map +1 -0
- package/dist/tests/e2e/install-flow.e2e.test.d.ts +10 -0
- package/dist/tests/e2e/install-flow.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/install-flow.e2e.test.js +209 -0
- package/dist/tests/e2e/install-flow.e2e.test.js.map +1 -0
- package/dist/tests/e2e/recommend.e2e.test.d.ts +12 -0
- package/dist/tests/e2e/recommend.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/recommend.e2e.test.js +347 -0
- package/dist/tests/e2e/recommend.e2e.test.js.map +1 -0
- package/dist/tests/e2e/skill-flow.e2e.test.d.ts +10 -0
- package/dist/tests/e2e/skill-flow.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/skill-flow.e2e.test.js +280 -0
- package/dist/tests/e2e/skill-flow.e2e.test.js.map +1 -0
- package/dist/tests/e2e/suggest.e2e.test.d.ts +13 -0
- package/dist/tests/e2e/suggest.e2e.test.d.ts.map +1 -0
- package/dist/tests/e2e/suggest.e2e.test.js +347 -0
- package/dist/tests/e2e/suggest.e2e.test.js.map +1 -0
- package/dist/tests/e2e/utils/baseline-collector.d.ts +107 -0
- package/dist/tests/e2e/utils/baseline-collector.d.ts.map +1 -0
- package/dist/tests/e2e/utils/baseline-collector.js +211 -0
- package/dist/tests/e2e/utils/baseline-collector.js.map +1 -0
- package/dist/tests/e2e/utils/hardcoded-detector.d.ts +46 -0
- package/dist/tests/e2e/utils/hardcoded-detector.d.ts.map +1 -0
- package/dist/tests/e2e/utils/hardcoded-detector.js +255 -0
- package/dist/tests/e2e/utils/hardcoded-detector.js.map +1 -0
- package/dist/tests/e2e/utils/index.d.ts +7 -0
- package/dist/tests/e2e/utils/index.d.ts.map +1 -0
- package/dist/tests/e2e/utils/index.js +7 -0
- package/dist/tests/e2e/utils/index.js.map +1 -0
- package/dist/tests/e2e/utils/linear-reporter.d.ts +60 -0
- package/dist/tests/e2e/utils/linear-reporter.d.ts.map +1 -0
- package/dist/tests/e2e/utils/linear-reporter.js +232 -0
- package/dist/tests/e2e/utils/linear-reporter.js.map +1 -0
- package/dist/tests/health.test.d.ts +9 -0
- package/dist/tests/health.test.d.ts.map +1 -0
- package/dist/tests/health.test.js +308 -0
- package/dist/tests/health.test.js.map +1 -0
- package/dist/tests/integration/analyze.integration.test.d.ts +2 -0
- package/dist/tests/integration/analyze.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/analyze.integration.test.js +244 -0
- package/dist/tests/integration/analyze.integration.test.js.map +1 -0
- package/dist/tests/integration/compare.integration.test.d.ts +2 -0
- package/dist/tests/integration/compare.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/compare.integration.test.js +120 -0
- package/dist/tests/integration/compare.integration.test.js.map +1 -0
- package/dist/tests/integration/fixtures/test-skills.d.ts +62 -0
- package/dist/tests/integration/fixtures/test-skills.d.ts.map +1 -0
- package/dist/tests/integration/fixtures/test-skills.js +644 -0
- package/dist/tests/integration/fixtures/test-skills.js.map +1 -0
- package/dist/tests/integration/get-skill.integration.test.d.ts +6 -0
- package/dist/tests/integration/get-skill.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/get-skill.integration.test.js +203 -0
- package/dist/tests/integration/get-skill.integration.test.js.map +1 -0
- package/dist/tests/integration/github-api.integration.test.d.ts +14 -0
- package/dist/tests/integration/github-api.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/github-api.integration.test.js +190 -0
- package/dist/tests/integration/github-api.integration.test.js.map +1 -0
- package/dist/tests/integration/install.integration.test.d.ts +6 -0
- package/dist/tests/integration/install.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/install.integration.test.js +282 -0
- package/dist/tests/integration/install.integration.test.js.map +1 -0
- package/dist/tests/integration/recommend.integration.test.d.ts +2 -0
- package/dist/tests/integration/recommend.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/recommend.integration.test.js +215 -0
- package/dist/tests/integration/recommend.integration.test.js.map +1 -0
- package/dist/tests/integration/search.integration.test.d.ts +6 -0
- package/dist/tests/integration/search.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/search.integration.test.js +229 -0
- package/dist/tests/integration/search.integration.test.js.map +1 -0
- package/dist/tests/integration/setup.d.ts +71 -0
- package/dist/tests/integration/setup.d.ts.map +1 -0
- package/dist/tests/integration/setup.js +124 -0
- package/dist/tests/integration/setup.js.map +1 -0
- package/dist/tests/integration/uninstall.integration.test.d.ts +6 -0
- package/dist/tests/integration/uninstall.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/uninstall.integration.test.js +296 -0
- package/dist/tests/integration/uninstall.integration.test.js.map +1 -0
- package/dist/tests/integration/validate.integration.test.d.ts +2 -0
- package/dist/tests/integration/validate.integration.test.d.ts.map +1 -0
- package/dist/tests/integration/validate.integration.test.js +181 -0
- package/dist/tests/integration/validate.integration.test.js.map +1 -0
- package/dist/tests/onboarding/first-run.test.d.ts +7 -0
- package/dist/tests/onboarding/first-run.test.d.ts.map +1 -0
- package/dist/tests/onboarding/first-run.test.js +258 -0
- package/dist/tests/onboarding/first-run.test.js.map +1 -0
- package/dist/tests/performance/search-performance.test.d.ts +10 -0
- package/dist/tests/performance/search-performance.test.d.ts.map +1 -0
- package/dist/tests/performance/search-performance.test.js +218 -0
- package/dist/tests/performance/search-performance.test.js.map +1 -0
- package/dist/tests/recommend.test.d.ts +6 -0
- package/dist/tests/recommend.test.d.ts.map +1 -0
- package/dist/tests/recommend.test.js +208 -0
- package/dist/tests/recommend.test.js.map +1 -0
- package/dist/tests/suggestions/suggestion-engine.test.d.ts +6 -0
- package/dist/tests/suggestions/suggestion-engine.test.d.ts.map +1 -0
- package/dist/tests/suggestions/suggestion-engine.test.js +448 -0
- package/dist/tests/suggestions/suggestion-engine.test.js.map +1 -0
- package/dist/tests/test-utils.d.ts +74 -0
- package/dist/tests/test-utils.d.ts.map +1 -0
- package/dist/tests/test-utils.js +98 -0
- package/dist/tests/test-utils.js.map +1 -0
- package/dist/tests/tools.test.d.ts +5 -0
- package/dist/tests/tools.test.d.ts.map +1 -0
- package/dist/tests/tools.test.js +138 -0
- package/dist/tests/tools.test.js.map +1 -0
- package/dist/tests/unit/installed-skills.test.d.ts +6 -0
- package/dist/tests/unit/installed-skills.test.d.ts.map +1 -0
- package/dist/tests/unit/installed-skills.test.js +285 -0
- package/dist/tests/unit/installed-skills.test.js.map +1 -0
- package/dist/tests/unit/logger.test.d.ts +6 -0
- package/dist/tests/unit/logger.test.d.ts.map +1 -0
- package/dist/tests/unit/logger.test.js +281 -0
- package/dist/tests/unit/logger.test.js.map +1 -0
- package/dist/tests/validate.test.d.ts +5 -0
- package/dist/tests/validate.test.d.ts.map +1 -0
- package/dist/tests/validate.test.js +303 -0
- package/dist/tests/validate.test.js.map +1 -0
- package/dist/tests/webhooks/proxy-trust.security.test.d.ts +8 -0
- package/dist/tests/webhooks/proxy-trust.security.test.d.ts.map +1 -0
- package/dist/tests/webhooks/proxy-trust.security.test.js +145 -0
- package/dist/tests/webhooks/proxy-trust.security.test.js.map +1 -0
- package/dist/tests/webhooks/rate-limiter.security.test.d.ts +8 -0
- package/dist/tests/webhooks/rate-limiter.security.test.d.ts.map +1 -0
- package/dist/tests/webhooks/rate-limiter.security.test.js +122 -0
- package/dist/tests/webhooks/rate-limiter.security.test.js.map +1 -0
- package/dist/vitest.config.d.ts +6 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +13 -0
- package/dist/vitest.config.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for SMI-912: Project context detection for skill suggestions
|
|
3
|
+
* Tests all project detection functions comprehensively
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
import { detectProjectContext, detectDocker, detectLinear, detectGitHub, detectTestFramework, detectApiFramework, detectNativeModules, detectLanguage, getSuggestedSkills, validatePath, } from '../../src/context/project-detector.js';
|
|
10
|
+
// Create a unique temp directory for testing
|
|
11
|
+
const TEST_TEMP_DIR = path.join(os.tmpdir(), 'skillsmith-context-test-' + Date.now());
|
|
12
|
+
/**
|
|
13
|
+
* Create a mock project directory structure
|
|
14
|
+
*/
|
|
15
|
+
function createMockProject(options) {
|
|
16
|
+
const projectDir = path.join(TEST_TEMP_DIR, 'project-' + Date.now());
|
|
17
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
18
|
+
if (options.dockerfile) {
|
|
19
|
+
fs.writeFileSync(path.join(projectDir, 'Dockerfile'), 'FROM node:20');
|
|
20
|
+
}
|
|
21
|
+
if (options.dockerCompose) {
|
|
22
|
+
const ext = options.dockerCompose === 'yaml' ? 'yaml' : 'yml';
|
|
23
|
+
fs.writeFileSync(path.join(projectDir, `docker-compose.${ext}`), 'version: "3"');
|
|
24
|
+
}
|
|
25
|
+
if (options.gitConfig) {
|
|
26
|
+
const gitDir = path.join(projectDir, '.git');
|
|
27
|
+
fs.mkdirSync(gitDir, { recursive: true });
|
|
28
|
+
fs.writeFileSync(path.join(gitDir, 'config'), options.gitConfig);
|
|
29
|
+
}
|
|
30
|
+
if (options.packageJson) {
|
|
31
|
+
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify(options.packageJson, null, 2));
|
|
32
|
+
}
|
|
33
|
+
if (options.tsconfig) {
|
|
34
|
+
fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), JSON.stringify({ compilerOptions: { strict: true } }, null, 2));
|
|
35
|
+
}
|
|
36
|
+
if (options.requirements) {
|
|
37
|
+
fs.writeFileSync(path.join(projectDir, 'requirements.txt'), options.requirements);
|
|
38
|
+
}
|
|
39
|
+
if (options.pyproject) {
|
|
40
|
+
fs.writeFileSync(path.join(projectDir, 'pyproject.toml'), options.pyproject);
|
|
41
|
+
}
|
|
42
|
+
return projectDir;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Clean up test directories
|
|
46
|
+
*/
|
|
47
|
+
function cleanupTestDir() {
|
|
48
|
+
if (fs.existsSync(TEST_TEMP_DIR)) {
|
|
49
|
+
fs.rmSync(TEST_TEMP_DIR, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
describe('project-detector', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
cleanupTestDir();
|
|
55
|
+
fs.mkdirSync(TEST_TEMP_DIR, { recursive: true });
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
cleanupTestDir();
|
|
59
|
+
});
|
|
60
|
+
describe('detectDocker', () => {
|
|
61
|
+
it('should detect Dockerfile', () => {
|
|
62
|
+
const projectDir = createMockProject({ dockerfile: true });
|
|
63
|
+
expect(detectDocker(projectDir)).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
it('should detect docker-compose.yml', () => {
|
|
66
|
+
const projectDir = createMockProject({ dockerCompose: 'yml' });
|
|
67
|
+
expect(detectDocker(projectDir)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
it('should detect docker-compose.yaml', () => {
|
|
70
|
+
const projectDir = createMockProject({ dockerCompose: 'yaml' });
|
|
71
|
+
expect(detectDocker(projectDir)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it('should return false when no Docker files exist', () => {
|
|
74
|
+
const projectDir = createMockProject({});
|
|
75
|
+
expect(detectDocker(projectDir)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
it('should return false for non-existent directory', () => {
|
|
78
|
+
expect(detectDocker('/non/existent/path')).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe('detectLinear', () => {
|
|
82
|
+
it('should detect Linear integration from git config', () => {
|
|
83
|
+
const projectDir = createMockProject({
|
|
84
|
+
gitConfig: `[core]
|
|
85
|
+
repositoryformatversion = 0
|
|
86
|
+
[remote "origin"]
|
|
87
|
+
url = https://linear.app/myorg/project-123
|
|
88
|
+
fetch = +refs/heads/*:refs/remotes/origin/*`,
|
|
89
|
+
});
|
|
90
|
+
expect(detectLinear(projectDir)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('should return false when no git config exists', () => {
|
|
93
|
+
const projectDir = createMockProject({});
|
|
94
|
+
expect(detectLinear(projectDir)).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
it('should return false when git config has no linear.app', () => {
|
|
97
|
+
const projectDir = createMockProject({
|
|
98
|
+
gitConfig: `[core]
|
|
99
|
+
repositoryformatversion = 0
|
|
100
|
+
[remote "origin"]
|
|
101
|
+
url = https://github.com/user/repo.git`,
|
|
102
|
+
});
|
|
103
|
+
expect(detectLinear(projectDir)).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
it('should return false for non-existent directory', () => {
|
|
106
|
+
expect(detectLinear('/non/existent/path')).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('detectGitHub', () => {
|
|
110
|
+
it('should detect GitHub hosting from git config', () => {
|
|
111
|
+
const projectDir = createMockProject({
|
|
112
|
+
gitConfig: `[core]
|
|
113
|
+
repositoryformatversion = 0
|
|
114
|
+
[remote "origin"]
|
|
115
|
+
url = https://github.com/user/repo.git
|
|
116
|
+
fetch = +refs/heads/*:refs/remotes/origin/*`,
|
|
117
|
+
});
|
|
118
|
+
expect(detectGitHub(projectDir)).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
it('should detect GitHub SSH URLs', () => {
|
|
121
|
+
const projectDir = createMockProject({
|
|
122
|
+
gitConfig: `[remote "origin"]
|
|
123
|
+
url = git@github.com:user/repo.git`,
|
|
124
|
+
});
|
|
125
|
+
expect(detectGitHub(projectDir)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
it('should return false when no git config exists', () => {
|
|
128
|
+
const projectDir = createMockProject({});
|
|
129
|
+
expect(detectGitHub(projectDir)).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
it('should return false when git config has no github.com', () => {
|
|
132
|
+
const projectDir = createMockProject({
|
|
133
|
+
gitConfig: `[remote "origin"]
|
|
134
|
+
url = https://gitlab.com/user/repo.git`,
|
|
135
|
+
});
|
|
136
|
+
expect(detectGitHub(projectDir)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
it('should return false for non-existent directory', () => {
|
|
139
|
+
expect(detectGitHub('/non/existent/path')).toBe(false);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('detectTestFramework', () => {
|
|
143
|
+
it('should detect vitest from devDependencies', () => {
|
|
144
|
+
const projectDir = createMockProject({
|
|
145
|
+
packageJson: {
|
|
146
|
+
name: 'test-project',
|
|
147
|
+
devDependencies: { vitest: '^1.0.0' },
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
expect(detectTestFramework(projectDir)).toBe('vitest');
|
|
151
|
+
});
|
|
152
|
+
it('should detect jest from dependencies', () => {
|
|
153
|
+
const projectDir = createMockProject({
|
|
154
|
+
packageJson: {
|
|
155
|
+
name: 'test-project',
|
|
156
|
+
dependencies: { jest: '^29.0.0' },
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
expect(detectTestFramework(projectDir)).toBe('jest');
|
|
160
|
+
});
|
|
161
|
+
it('should detect mocha from devDependencies', () => {
|
|
162
|
+
const projectDir = createMockProject({
|
|
163
|
+
packageJson: {
|
|
164
|
+
name: 'test-project',
|
|
165
|
+
devDependencies: { mocha: '^10.0.0' },
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
expect(detectTestFramework(projectDir)).toBe('mocha');
|
|
169
|
+
});
|
|
170
|
+
it('should prefer vitest over jest when both exist', () => {
|
|
171
|
+
const projectDir = createMockProject({
|
|
172
|
+
packageJson: {
|
|
173
|
+
name: 'test-project',
|
|
174
|
+
devDependencies: { vitest: '^1.0.0', jest: '^29.0.0' },
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
expect(detectTestFramework(projectDir)).toBe('vitest');
|
|
178
|
+
});
|
|
179
|
+
it('should return null when no package.json exists', () => {
|
|
180
|
+
const projectDir = createMockProject({});
|
|
181
|
+
expect(detectTestFramework(projectDir)).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
it('should return null when no test framework is found', () => {
|
|
184
|
+
const projectDir = createMockProject({
|
|
185
|
+
packageJson: {
|
|
186
|
+
name: 'test-project',
|
|
187
|
+
dependencies: { express: '^4.0.0' },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
expect(detectTestFramework(projectDir)).toBeNull();
|
|
191
|
+
});
|
|
192
|
+
it('should handle invalid JSON gracefully', () => {
|
|
193
|
+
const projectDir = path.join(TEST_TEMP_DIR, 'invalid-json');
|
|
194
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
195
|
+
fs.writeFileSync(path.join(projectDir, 'package.json'), '{ invalid json }');
|
|
196
|
+
expect(detectTestFramework(projectDir)).toBeNull();
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
describe('detectApiFramework', () => {
|
|
200
|
+
it('should detect nextjs from dependencies', () => {
|
|
201
|
+
const projectDir = createMockProject({
|
|
202
|
+
packageJson: {
|
|
203
|
+
name: 'next-app',
|
|
204
|
+
dependencies: { next: '^14.0.0', react: '^18.0.0' },
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
expect(detectApiFramework(projectDir)).toBe('nextjs');
|
|
208
|
+
});
|
|
209
|
+
it('should detect express from dependencies', () => {
|
|
210
|
+
const projectDir = createMockProject({
|
|
211
|
+
packageJson: {
|
|
212
|
+
name: 'express-app',
|
|
213
|
+
dependencies: { express: '^4.18.0' },
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
expect(detectApiFramework(projectDir)).toBe('express');
|
|
217
|
+
});
|
|
218
|
+
it('should prefer nextjs over express when both exist', () => {
|
|
219
|
+
const projectDir = createMockProject({
|
|
220
|
+
packageJson: {
|
|
221
|
+
name: 'full-stack',
|
|
222
|
+
dependencies: { next: '^14.0.0', express: '^4.18.0' },
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
expect(detectApiFramework(projectDir)).toBe('nextjs');
|
|
226
|
+
});
|
|
227
|
+
it('should detect fastapi from requirements.txt', () => {
|
|
228
|
+
const projectDir = createMockProject({
|
|
229
|
+
requirements: `fastapi==0.104.0
|
|
230
|
+
uvicorn==0.24.0
|
|
231
|
+
pydantic==2.5.0`,
|
|
232
|
+
});
|
|
233
|
+
expect(detectApiFramework(projectDir)).toBe('fastapi');
|
|
234
|
+
});
|
|
235
|
+
it('should detect fastapi from pyproject.toml', () => {
|
|
236
|
+
const projectDir = createMockProject({
|
|
237
|
+
pyproject: `[project]
|
|
238
|
+
name = "my-api"
|
|
239
|
+
dependencies = [
|
|
240
|
+
"fastapi>=0.104.0",
|
|
241
|
+
"uvicorn>=0.24.0",
|
|
242
|
+
]`,
|
|
243
|
+
});
|
|
244
|
+
expect(detectApiFramework(projectDir)).toBe('fastapi');
|
|
245
|
+
});
|
|
246
|
+
it('should return null when no API framework is found', () => {
|
|
247
|
+
const projectDir = createMockProject({
|
|
248
|
+
packageJson: {
|
|
249
|
+
name: 'cli-app',
|
|
250
|
+
dependencies: { commander: '^11.0.0' },
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
expect(detectApiFramework(projectDir)).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
it('should return null for empty directory', () => {
|
|
256
|
+
const projectDir = createMockProject({});
|
|
257
|
+
expect(detectApiFramework(projectDir)).toBeNull();
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
describe('detectNativeModules', () => {
|
|
261
|
+
it('should detect better-sqlite3', () => {
|
|
262
|
+
const projectDir = createMockProject({
|
|
263
|
+
packageJson: {
|
|
264
|
+
name: 'native-app',
|
|
265
|
+
dependencies: { 'better-sqlite3': '^9.0.0' },
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
expect(detectNativeModules(projectDir)).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
it('should detect sharp in devDependencies', () => {
|
|
271
|
+
const projectDir = createMockProject({
|
|
272
|
+
packageJson: {
|
|
273
|
+
name: 'image-app',
|
|
274
|
+
devDependencies: { sharp: '^0.33.0' },
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
expect(detectNativeModules(projectDir)).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
it('should detect onnxruntime-node', () => {
|
|
280
|
+
const projectDir = createMockProject({
|
|
281
|
+
packageJson: {
|
|
282
|
+
name: 'ml-app',
|
|
283
|
+
dependencies: { 'onnxruntime-node': '^1.16.0' },
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
expect(detectNativeModules(projectDir)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
it('should detect bcrypt', () => {
|
|
289
|
+
const projectDir = createMockProject({
|
|
290
|
+
packageJson: {
|
|
291
|
+
name: 'auth-app',
|
|
292
|
+
dependencies: { bcrypt: '^5.1.0' },
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
expect(detectNativeModules(projectDir)).toBe(true);
|
|
296
|
+
});
|
|
297
|
+
it('should detect canvas', () => {
|
|
298
|
+
const projectDir = createMockProject({
|
|
299
|
+
packageJson: {
|
|
300
|
+
name: 'graphics-app',
|
|
301
|
+
dependencies: { canvas: '^2.11.0' },
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
expect(detectNativeModules(projectDir)).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
it('should return false when no native modules exist', () => {
|
|
307
|
+
const projectDir = createMockProject({
|
|
308
|
+
packageJson: {
|
|
309
|
+
name: 'pure-js-app',
|
|
310
|
+
dependencies: { express: '^4.18.0', lodash: '^4.17.0' },
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
expect(detectNativeModules(projectDir)).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
it('should return false when no package.json exists', () => {
|
|
316
|
+
const projectDir = createMockProject({});
|
|
317
|
+
expect(detectNativeModules(projectDir)).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
describe('detectLanguage', () => {
|
|
321
|
+
it('should detect TypeScript from tsconfig.json', () => {
|
|
322
|
+
const projectDir = createMockProject({
|
|
323
|
+
tsconfig: true,
|
|
324
|
+
packageJson: { name: 'ts-app' },
|
|
325
|
+
});
|
|
326
|
+
expect(detectLanguage(projectDir)).toBe('typescript');
|
|
327
|
+
});
|
|
328
|
+
it('should detect JavaScript from package.json (no tsconfig)', () => {
|
|
329
|
+
const projectDir = createMockProject({
|
|
330
|
+
packageJson: { name: 'js-app' },
|
|
331
|
+
});
|
|
332
|
+
expect(detectLanguage(projectDir)).toBe('javascript');
|
|
333
|
+
});
|
|
334
|
+
it('should detect Python from requirements.txt', () => {
|
|
335
|
+
const projectDir = createMockProject({
|
|
336
|
+
requirements: 'flask==2.3.0\nrequests==2.31.0',
|
|
337
|
+
});
|
|
338
|
+
expect(detectLanguage(projectDir)).toBe('python');
|
|
339
|
+
});
|
|
340
|
+
it('should detect Python from pyproject.toml', () => {
|
|
341
|
+
const projectDir = createMockProject({
|
|
342
|
+
pyproject: '[project]\nname = "my-python-app"',
|
|
343
|
+
});
|
|
344
|
+
expect(detectLanguage(projectDir)).toBe('python');
|
|
345
|
+
});
|
|
346
|
+
it('should prefer TypeScript over JavaScript', () => {
|
|
347
|
+
const projectDir = createMockProject({
|
|
348
|
+
tsconfig: true,
|
|
349
|
+
packageJson: { name: 'ts-app' },
|
|
350
|
+
});
|
|
351
|
+
expect(detectLanguage(projectDir)).toBe('typescript');
|
|
352
|
+
});
|
|
353
|
+
it('should return null for empty directory', () => {
|
|
354
|
+
const projectDir = createMockProject({});
|
|
355
|
+
expect(detectLanguage(projectDir)).toBeNull();
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
describe('detectProjectContext', () => {
|
|
359
|
+
it('should detect full project context', () => {
|
|
360
|
+
const projectDir = createMockProject({
|
|
361
|
+
dockerfile: true,
|
|
362
|
+
gitConfig: `[remote "origin"]
|
|
363
|
+
url = https://github.com/user/repo.git`,
|
|
364
|
+
packageJson: {
|
|
365
|
+
name: 'full-stack-app',
|
|
366
|
+
dependencies: {
|
|
367
|
+
next: '^14.0.0',
|
|
368
|
+
'better-sqlite3': '^9.0.0',
|
|
369
|
+
},
|
|
370
|
+
devDependencies: {
|
|
371
|
+
vitest: '^1.0.0',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
tsconfig: true,
|
|
375
|
+
});
|
|
376
|
+
const context = detectProjectContext(projectDir);
|
|
377
|
+
expect(context.hasDocker).toBe(true);
|
|
378
|
+
expect(context.hasGitHub).toBe(true);
|
|
379
|
+
expect(context.hasLinear).toBe(false);
|
|
380
|
+
expect(context.testFramework).toBe('vitest');
|
|
381
|
+
expect(context.apiFramework).toBe('nextjs');
|
|
382
|
+
expect(context.hasNativeModules).toBe(true);
|
|
383
|
+
expect(context.language).toBe('typescript');
|
|
384
|
+
});
|
|
385
|
+
it('should handle minimal project', () => {
|
|
386
|
+
const projectDir = createMockProject({
|
|
387
|
+
packageJson: { name: 'minimal-app' },
|
|
388
|
+
});
|
|
389
|
+
const context = detectProjectContext(projectDir);
|
|
390
|
+
expect(context.hasDocker).toBe(false);
|
|
391
|
+
expect(context.hasGitHub).toBe(false);
|
|
392
|
+
expect(context.hasLinear).toBe(false);
|
|
393
|
+
expect(context.testFramework).toBeNull();
|
|
394
|
+
expect(context.apiFramework).toBeNull();
|
|
395
|
+
expect(context.hasNativeModules).toBe(false);
|
|
396
|
+
expect(context.language).toBe('javascript');
|
|
397
|
+
});
|
|
398
|
+
it('should handle empty directory', () => {
|
|
399
|
+
const projectDir = createMockProject({});
|
|
400
|
+
const context = detectProjectContext(projectDir);
|
|
401
|
+
expect(context.hasDocker).toBe(false);
|
|
402
|
+
expect(context.hasGitHub).toBe(false);
|
|
403
|
+
expect(context.hasLinear).toBe(false);
|
|
404
|
+
expect(context.testFramework).toBeNull();
|
|
405
|
+
expect(context.apiFramework).toBeNull();
|
|
406
|
+
expect(context.hasNativeModules).toBe(false);
|
|
407
|
+
expect(context.language).toBeNull();
|
|
408
|
+
});
|
|
409
|
+
it('should detect Python FastAPI project', () => {
|
|
410
|
+
const projectDir = createMockProject({
|
|
411
|
+
dockerfile: true,
|
|
412
|
+
requirements: `fastapi==0.104.0
|
|
413
|
+
uvicorn==0.24.0
|
|
414
|
+
pydantic==2.5.0`,
|
|
415
|
+
gitConfig: `[remote "origin"]
|
|
416
|
+
url = https://github.com/user/api.git`,
|
|
417
|
+
});
|
|
418
|
+
const context = detectProjectContext(projectDir);
|
|
419
|
+
expect(context.hasDocker).toBe(true);
|
|
420
|
+
expect(context.hasGitHub).toBe(true);
|
|
421
|
+
expect(context.apiFramework).toBe('fastapi');
|
|
422
|
+
expect(context.language).toBe('python');
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
describe('getSuggestedSkills', () => {
|
|
426
|
+
it('should suggest docker skill for Docker projects', () => {
|
|
427
|
+
const context = {
|
|
428
|
+
hasDocker: true,
|
|
429
|
+
hasLinear: false,
|
|
430
|
+
hasGitHub: false,
|
|
431
|
+
testFramework: null,
|
|
432
|
+
apiFramework: null,
|
|
433
|
+
hasNativeModules: false,
|
|
434
|
+
language: null,
|
|
435
|
+
};
|
|
436
|
+
const suggestions = getSuggestedSkills(context);
|
|
437
|
+
expect(suggestions).toContain('docker');
|
|
438
|
+
});
|
|
439
|
+
it('should suggest github skills for GitHub projects', () => {
|
|
440
|
+
const context = {
|
|
441
|
+
hasDocker: false,
|
|
442
|
+
hasLinear: false,
|
|
443
|
+
hasGitHub: true,
|
|
444
|
+
testFramework: null,
|
|
445
|
+
apiFramework: null,
|
|
446
|
+
hasNativeModules: false,
|
|
447
|
+
language: null,
|
|
448
|
+
};
|
|
449
|
+
const suggestions = getSuggestedSkills(context);
|
|
450
|
+
expect(suggestions).toContain('github-actions');
|
|
451
|
+
expect(suggestions).toContain('github-pr');
|
|
452
|
+
});
|
|
453
|
+
it('should suggest linear skill for Linear-integrated projects', () => {
|
|
454
|
+
const context = {
|
|
455
|
+
hasDocker: false,
|
|
456
|
+
hasLinear: true,
|
|
457
|
+
hasGitHub: false,
|
|
458
|
+
testFramework: null,
|
|
459
|
+
apiFramework: null,
|
|
460
|
+
hasNativeModules: false,
|
|
461
|
+
language: null,
|
|
462
|
+
};
|
|
463
|
+
const suggestions = getSuggestedSkills(context);
|
|
464
|
+
expect(suggestions).toContain('linear');
|
|
465
|
+
});
|
|
466
|
+
it('should suggest jest-helper for Jest projects', () => {
|
|
467
|
+
const context = {
|
|
468
|
+
hasDocker: false,
|
|
469
|
+
hasLinear: false,
|
|
470
|
+
hasGitHub: false,
|
|
471
|
+
testFramework: 'jest',
|
|
472
|
+
apiFramework: null,
|
|
473
|
+
hasNativeModules: false,
|
|
474
|
+
language: null,
|
|
475
|
+
};
|
|
476
|
+
const suggestions = getSuggestedSkills(context);
|
|
477
|
+
expect(suggestions).toContain('jest-helper');
|
|
478
|
+
});
|
|
479
|
+
it('should suggest vitest-helper for Vitest projects', () => {
|
|
480
|
+
const context = {
|
|
481
|
+
hasDocker: false,
|
|
482
|
+
hasLinear: false,
|
|
483
|
+
hasGitHub: false,
|
|
484
|
+
testFramework: 'vitest',
|
|
485
|
+
apiFramework: null,
|
|
486
|
+
hasNativeModules: false,
|
|
487
|
+
language: null,
|
|
488
|
+
};
|
|
489
|
+
const suggestions = getSuggestedSkills(context);
|
|
490
|
+
expect(suggestions).toContain('vitest-helper');
|
|
491
|
+
});
|
|
492
|
+
it('should suggest mocha-helper for Mocha projects', () => {
|
|
493
|
+
const context = {
|
|
494
|
+
hasDocker: false,
|
|
495
|
+
hasLinear: false,
|
|
496
|
+
hasGitHub: false,
|
|
497
|
+
testFramework: 'mocha',
|
|
498
|
+
apiFramework: null,
|
|
499
|
+
hasNativeModules: false,
|
|
500
|
+
language: null,
|
|
501
|
+
};
|
|
502
|
+
const suggestions = getSuggestedSkills(context);
|
|
503
|
+
expect(suggestions).toContain('mocha-helper');
|
|
504
|
+
});
|
|
505
|
+
it('should suggest nextjs skill for Next.js projects', () => {
|
|
506
|
+
const context = {
|
|
507
|
+
hasDocker: false,
|
|
508
|
+
hasLinear: false,
|
|
509
|
+
hasGitHub: false,
|
|
510
|
+
testFramework: null,
|
|
511
|
+
apiFramework: 'nextjs',
|
|
512
|
+
hasNativeModules: false,
|
|
513
|
+
language: null,
|
|
514
|
+
};
|
|
515
|
+
const suggestions = getSuggestedSkills(context);
|
|
516
|
+
expect(suggestions).toContain('nextjs');
|
|
517
|
+
});
|
|
518
|
+
it('should suggest express skill for Express projects', () => {
|
|
519
|
+
const context = {
|
|
520
|
+
hasDocker: false,
|
|
521
|
+
hasLinear: false,
|
|
522
|
+
hasGitHub: false,
|
|
523
|
+
testFramework: null,
|
|
524
|
+
apiFramework: 'express',
|
|
525
|
+
hasNativeModules: false,
|
|
526
|
+
language: null,
|
|
527
|
+
};
|
|
528
|
+
const suggestions = getSuggestedSkills(context);
|
|
529
|
+
expect(suggestions).toContain('express');
|
|
530
|
+
});
|
|
531
|
+
it('should suggest fastapi skill for FastAPI projects', () => {
|
|
532
|
+
const context = {
|
|
533
|
+
hasDocker: false,
|
|
534
|
+
hasLinear: false,
|
|
535
|
+
hasGitHub: false,
|
|
536
|
+
testFramework: null,
|
|
537
|
+
apiFramework: 'fastapi',
|
|
538
|
+
hasNativeModules: false,
|
|
539
|
+
language: null,
|
|
540
|
+
};
|
|
541
|
+
const suggestions = getSuggestedSkills(context);
|
|
542
|
+
expect(suggestions).toContain('fastapi');
|
|
543
|
+
});
|
|
544
|
+
it('should suggest native-modules skill for projects with native modules', () => {
|
|
545
|
+
const context = {
|
|
546
|
+
hasDocker: false,
|
|
547
|
+
hasLinear: false,
|
|
548
|
+
hasGitHub: false,
|
|
549
|
+
testFramework: null,
|
|
550
|
+
apiFramework: null,
|
|
551
|
+
hasNativeModules: true,
|
|
552
|
+
language: null,
|
|
553
|
+
};
|
|
554
|
+
const suggestions = getSuggestedSkills(context);
|
|
555
|
+
expect(suggestions).toContain('native-modules');
|
|
556
|
+
});
|
|
557
|
+
it('should suggest typescript skill for TypeScript projects', () => {
|
|
558
|
+
const context = {
|
|
559
|
+
hasDocker: false,
|
|
560
|
+
hasLinear: false,
|
|
561
|
+
hasGitHub: false,
|
|
562
|
+
testFramework: null,
|
|
563
|
+
apiFramework: null,
|
|
564
|
+
hasNativeModules: false,
|
|
565
|
+
language: 'typescript',
|
|
566
|
+
};
|
|
567
|
+
const suggestions = getSuggestedSkills(context);
|
|
568
|
+
expect(suggestions).toContain('typescript');
|
|
569
|
+
});
|
|
570
|
+
it('should suggest python skill for Python projects', () => {
|
|
571
|
+
const context = {
|
|
572
|
+
hasDocker: false,
|
|
573
|
+
hasLinear: false,
|
|
574
|
+
hasGitHub: false,
|
|
575
|
+
testFramework: null,
|
|
576
|
+
apiFramework: null,
|
|
577
|
+
hasNativeModules: false,
|
|
578
|
+
language: 'python',
|
|
579
|
+
};
|
|
580
|
+
const suggestions = getSuggestedSkills(context);
|
|
581
|
+
expect(suggestions).toContain('python');
|
|
582
|
+
});
|
|
583
|
+
it('should return multiple suggestions for complex projects', () => {
|
|
584
|
+
const context = {
|
|
585
|
+
hasDocker: true,
|
|
586
|
+
hasLinear: false,
|
|
587
|
+
hasGitHub: true,
|
|
588
|
+
testFramework: 'vitest',
|
|
589
|
+
apiFramework: 'nextjs',
|
|
590
|
+
hasNativeModules: true,
|
|
591
|
+
language: 'typescript',
|
|
592
|
+
};
|
|
593
|
+
const suggestions = getSuggestedSkills(context);
|
|
594
|
+
expect(suggestions).toContain('docker');
|
|
595
|
+
expect(suggestions).toContain('github-actions');
|
|
596
|
+
expect(suggestions).toContain('github-pr');
|
|
597
|
+
expect(suggestions).toContain('vitest-helper');
|
|
598
|
+
expect(suggestions).toContain('nextjs');
|
|
599
|
+
expect(suggestions).toContain('native-modules');
|
|
600
|
+
expect(suggestions).toContain('typescript');
|
|
601
|
+
});
|
|
602
|
+
it('should return empty array for empty context', () => {
|
|
603
|
+
const context = {
|
|
604
|
+
hasDocker: false,
|
|
605
|
+
hasLinear: false,
|
|
606
|
+
hasGitHub: false,
|
|
607
|
+
testFramework: null,
|
|
608
|
+
apiFramework: null,
|
|
609
|
+
hasNativeModules: false,
|
|
610
|
+
language: null,
|
|
611
|
+
};
|
|
612
|
+
const suggestions = getSuggestedSkills(context);
|
|
613
|
+
expect(suggestions).toEqual([]);
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
describe('validatePath - path traversal prevention', () => {
|
|
617
|
+
it('should allow valid paths within base directory', () => {
|
|
618
|
+
const baseDir = TEST_TEMP_DIR;
|
|
619
|
+
const subDir = path.join(baseDir, 'subdir');
|
|
620
|
+
fs.mkdirSync(subDir, { recursive: true });
|
|
621
|
+
const result = validatePath('subdir', baseDir);
|
|
622
|
+
expect(result).toBe(subDir);
|
|
623
|
+
});
|
|
624
|
+
it('should allow the base directory itself', () => {
|
|
625
|
+
const baseDir = TEST_TEMP_DIR;
|
|
626
|
+
const result = validatePath(baseDir, baseDir);
|
|
627
|
+
expect(result).toBe(path.resolve(baseDir));
|
|
628
|
+
});
|
|
629
|
+
it('should reject paths with ../ that escape the base directory', () => {
|
|
630
|
+
const baseDir = path.join(TEST_TEMP_DIR, 'project');
|
|
631
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
632
|
+
expect(() => validatePath('../escape', baseDir)).toThrow('Path traversal attempt detected');
|
|
633
|
+
});
|
|
634
|
+
it('should reject absolute paths outside allowed directory', () => {
|
|
635
|
+
const baseDir = path.join(TEST_TEMP_DIR, 'project');
|
|
636
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
637
|
+
expect(() => validatePath('/etc/passwd', baseDir)).toThrow('Path traversal attempt detected');
|
|
638
|
+
});
|
|
639
|
+
it('should reject complex traversal attempts', () => {
|
|
640
|
+
const baseDir = path.join(TEST_TEMP_DIR, 'project');
|
|
641
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
642
|
+
expect(() => validatePath('subdir/../../escape', baseDir)).toThrow('Path traversal attempt detected');
|
|
643
|
+
});
|
|
644
|
+
it('should reject paths that look similar but are outside base', () => {
|
|
645
|
+
const baseDir = path.join(TEST_TEMP_DIR, 'base');
|
|
646
|
+
const similarDir = path.join(TEST_TEMP_DIR, 'base-other');
|
|
647
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
648
|
+
fs.mkdirSync(similarDir, { recursive: true });
|
|
649
|
+
expect(() => validatePath(similarDir, baseDir)).toThrow('Path traversal attempt detected');
|
|
650
|
+
});
|
|
651
|
+
it('should allow deeply nested valid paths', () => {
|
|
652
|
+
const baseDir = TEST_TEMP_DIR;
|
|
653
|
+
const deepPath = path.join(baseDir, 'a', 'b', 'c', 'd');
|
|
654
|
+
fs.mkdirSync(deepPath, { recursive: true });
|
|
655
|
+
const result = validatePath('a/b/c/d', baseDir);
|
|
656
|
+
expect(result).toBe(deepPath);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
describe('detectProjectContext - path traversal prevention', () => {
|
|
660
|
+
it('should throw when path tries to escape via relative traversal', () => {
|
|
661
|
+
const baseDir = path.join(TEST_TEMP_DIR, 'allowed');
|
|
662
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
663
|
+
expect(() => detectProjectContext('../escape', baseDir)).toThrow('Path traversal attempt detected');
|
|
664
|
+
});
|
|
665
|
+
it('should throw when absolute path is outside allowed base', () => {
|
|
666
|
+
const baseDir = path.join(TEST_TEMP_DIR, 'allowed');
|
|
667
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
668
|
+
expect(() => detectProjectContext('/tmp/outside', baseDir)).toThrow('Path traversal attempt detected');
|
|
669
|
+
});
|
|
670
|
+
it('should work normally for valid paths within allowed base', () => {
|
|
671
|
+
const baseDir = TEST_TEMP_DIR;
|
|
672
|
+
const projectDir = createMockProject({ dockerfile: true });
|
|
673
|
+
// Project is within TEST_TEMP_DIR, so this should work
|
|
674
|
+
const context = detectProjectContext(projectDir, baseDir);
|
|
675
|
+
expect(context.hasDocker).toBe(true);
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
describe('edge cases', () => {
|
|
679
|
+
it('should handle read-only file systems gracefully', () => {
|
|
680
|
+
// Non-existent paths should return default values, not throw
|
|
681
|
+
const context = detectProjectContext('/this/path/does/not/exist');
|
|
682
|
+
expect(context.hasDocker).toBe(false);
|
|
683
|
+
expect(context.hasGitHub).toBe(false);
|
|
684
|
+
expect(context.language).toBeNull();
|
|
685
|
+
});
|
|
686
|
+
it('should handle corrupted package.json', () => {
|
|
687
|
+
const projectDir = path.join(TEST_TEMP_DIR, 'corrupt-json');
|
|
688
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
689
|
+
fs.writeFileSync(path.join(projectDir, 'package.json'), '{ "name": incomplete');
|
|
690
|
+
expect(detectTestFramework(projectDir)).toBeNull();
|
|
691
|
+
expect(detectApiFramework(projectDir)).toBeNull();
|
|
692
|
+
expect(detectNativeModules(projectDir)).toBe(false);
|
|
693
|
+
});
|
|
694
|
+
it('should handle package.json with null dependencies', () => {
|
|
695
|
+
const projectDir = createMockProject({
|
|
696
|
+
packageJson: {
|
|
697
|
+
name: 'null-deps',
|
|
698
|
+
dependencies: null,
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
// Should not throw
|
|
702
|
+
expect(detectTestFramework(projectDir)).toBeNull();
|
|
703
|
+
});
|
|
704
|
+
it('should handle empty git config file', () => {
|
|
705
|
+
const projectDir = createMockProject({
|
|
706
|
+
gitConfig: '',
|
|
707
|
+
});
|
|
708
|
+
expect(detectGitHub(projectDir)).toBe(false);
|
|
709
|
+
expect(detectLinear(projectDir)).toBe(false);
|
|
710
|
+
});
|
|
711
|
+
it('should handle case-insensitive fastapi detection', () => {
|
|
712
|
+
const projectDir = createMockProject({
|
|
713
|
+
requirements: 'FastAPI==0.104.0\nUvicorn==0.24.0',
|
|
714
|
+
});
|
|
715
|
+
expect(detectApiFramework(projectDir)).toBe('fastapi');
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
//# sourceMappingURL=project-detector.test.js.map
|