universal-dev-standards 5.1.0-beta.6 → 5.1.0-beta.7
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/bin/uds.js +12 -0
- package/bundled/ai/standards/agent-communication-protocol.ai.yaml +34 -0
- package/bundled/ai/standards/anti-sycophancy-prompting.ai.yaml +111 -0
- package/bundled/ai/standards/capability-declaration.ai.yaml +113 -0
- package/bundled/ai/standards/circuit-breaker.ai.yaml +93 -0
- package/bundled/ai/standards/developer-memory.ai.yaml +13 -0
- package/bundled/ai/standards/dual-phase-output.ai.yaml +108 -0
- package/bundled/ai/standards/failure-source-taxonomy.ai.yaml +115 -0
- package/bundled/ai/standards/frontend-design-standards.ai.yaml +305 -0
- package/bundled/ai/standards/health-check-standards.ai.yaml +140 -0
- package/bundled/ai/standards/immutability-first.ai.yaml +112 -0
- package/bundled/ai/standards/model-selection.ai.yaml +111 -3
- package/bundled/ai/standards/packaging-standards.ai.yaml +142 -0
- package/bundled/ai/standards/recovery-recipe-registry.ai.yaml +200 -0
- package/bundled/ai/standards/retry-standards.ai.yaml +134 -0
- package/bundled/ai/standards/security-decision.ai.yaml +87 -0
- package/bundled/ai/standards/skill-standard-alignment-check.ai.yaml +119 -0
- package/bundled/ai/standards/standard-admission-criteria.ai.yaml +107 -0
- package/bundled/ai/standards/standard-lifecycle-management.ai.yaml +144 -0
- package/bundled/ai/standards/timeout-standards.ai.yaml +104 -0
- package/bundled/ai/standards/token-budget.ai.yaml +108 -0
- package/bundled/core/anti-sycophancy-prompting.md +184 -0
- package/bundled/core/capability-declaration.md +59 -0
- package/bundled/core/circuit-breaker.md +58 -0
- package/bundled/core/developer-memory.md +29 -1
- package/bundled/core/dual-phase-output.md +56 -0
- package/bundled/core/failure-source-taxonomy.md +72 -0
- package/bundled/core/frontend-design-standards.md +474 -0
- package/bundled/core/health-check-standards.md +72 -0
- package/bundled/core/immutability-first.md +105 -0
- package/bundled/core/model-selection.md +80 -0
- package/bundled/core/packaging-standards.md +216 -0
- package/bundled/core/recovery-recipe-registry.md +69 -0
- package/bundled/core/retry-standards.md +62 -0
- package/bundled/core/security-decision.md +65 -0
- package/bundled/core/skill-standard-alignment-check.md +79 -0
- package/bundled/core/standard-admission-criteria.md +84 -0
- package/bundled/core/standard-lifecycle-management.md +94 -0
- package/bundled/core/timeout-standards.md +63 -0
- package/bundled/core/token-budget.md +58 -0
- package/bundled/locales/zh-CN/CHANGELOG.md +22 -3
- package/bundled/locales/zh-CN/README.md +1 -1
- package/bundled/locales/zh-TW/CHANGELOG.md +22 -3
- package/bundled/locales/zh-TW/README.md +1 -1
- package/bundled/locales/zh-TW/core/anti-sycophancy-prompting.md +184 -0
- package/bundled/locales/zh-TW/core/packaging-standards.md +224 -0
- package/bundled/skills/e2e-assistant/SKILL.md +19 -5
- package/bundled/skills/testing-guide/SKILL.md +5 -0
- package/bundled/skills/testing-guide/test-skeleton-templates.md +316 -0
- package/package.json +1 -1
- package/src/commands/config.js +9 -0
- package/src/commands/init.js +91 -46
- package/src/commands/mcp.js +26 -0
- package/src/commands/run-intent.js +66 -0
- package/src/commands/update.js +35 -4
- package/src/core/command-router.js +85 -0
- package/src/core/project-config.js +91 -0
- package/src/flows/init-flow.js +6 -1
- package/src/i18n/messages.js +6 -6
- package/src/mcp/__tests__/server.test.js +251 -0
- package/src/mcp/server.js +352 -0
- package/src/prompts/init.js +157 -1
- package/src/reconciler/actual-state-scanner.js +24 -0
- package/src/uninstallers/hook-uninstaller.js +32 -1
- package/src/utils/e2e-analyzer.js +88 -5
- package/src/utils/e2e-detector.js +73 -1
- package/src/utils/integration-generator.js +22 -3
- package/standards-registry.json +193 -5
package/src/prompts/init.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { select, checkbox, confirm as inquirerConfirm, Separator } from '@inquirer/prompts';
|
|
1
|
+
import { select, checkbox, confirm as inquirerConfirm, Separator, input } from '@inquirer/prompts';
|
|
2
|
+
import { existsSync, writeFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
2
4
|
import chalk from 'chalk';
|
|
3
5
|
import os from 'os';
|
|
4
6
|
import { t, setLanguage, detectLanguage } from '../i18n/messages.js';
|
|
@@ -1349,3 +1351,157 @@ export async function promptMethodology() {
|
|
|
1349
1351
|
|
|
1350
1352
|
return methodology;
|
|
1351
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1356
|
+
// Project Command Contract (uds.project.yaml) — XSPEC-029 Phase 3
|
|
1357
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* Detect project ecosystem and return suggested commands.
|
|
1361
|
+
* @param {string} projectPath
|
|
1362
|
+
* @returns {{ test: string, lint: string, build: string, security: string }}
|
|
1363
|
+
*/
|
|
1364
|
+
export function suggestCommands(projectPath) {
|
|
1365
|
+
const p = (f) => join(projectPath, f);
|
|
1366
|
+
|
|
1367
|
+
if (existsSync(p('package.json')))
|
|
1368
|
+
return { test: 'npm test', lint: 'npm run lint', build: 'npm run build', security: 'npm audit' };
|
|
1369
|
+
|
|
1370
|
+
if (existsSync(p('requirements.txt')) || existsSync(p('pyproject.toml')) || existsSync(p('setup.py')))
|
|
1371
|
+
return { test: 'python -m pytest', lint: 'python -m ruff check .', build: 'python -m build', security: 'pip-audit' };
|
|
1372
|
+
|
|
1373
|
+
if (existsSync(p('go.mod')))
|
|
1374
|
+
return { test: 'go test ./...', lint: 'go vet ./...', build: 'go build ./...', security: 'govulncheck ./...' };
|
|
1375
|
+
|
|
1376
|
+
if (existsSync(p('pom.xml')))
|
|
1377
|
+
return { test: 'mvn test', lint: 'mvn checkstyle:check', build: 'mvn package', security: 'mvn dependency-check:check' };
|
|
1378
|
+
|
|
1379
|
+
if (existsSync(p('build.gradle')) || existsSync(p('build.gradle.kts')))
|
|
1380
|
+
return { test: './gradlew test', lint: './gradlew checkstyleMain', build: './gradlew build', security: './gradlew dependencyCheckAnalyze' };
|
|
1381
|
+
|
|
1382
|
+
if (existsSync(p('Cargo.toml')))
|
|
1383
|
+
return { test: 'cargo test', lint: 'cargo clippy', build: 'cargo build', security: 'cargo audit' };
|
|
1384
|
+
|
|
1385
|
+
if (existsSync(p('Gemfile')))
|
|
1386
|
+
return { test: 'bundle exec rspec', lint: 'bundle exec rubocop', build: 'gem build *.gemspec', security: 'bundle audit' };
|
|
1387
|
+
|
|
1388
|
+
// C# — glob for .csproj / .sln
|
|
1389
|
+
try {
|
|
1390
|
+
const files = readdirSync(projectPath);
|
|
1391
|
+
if (files.some(f => f.endsWith('.csproj') || f.endsWith('.sln')))
|
|
1392
|
+
return { test: 'dotnet test', lint: 'dotnet format --verify-no-changes', build: 'dotnet build', security: 'dotnet list package --vulnerable' };
|
|
1393
|
+
} catch {
|
|
1394
|
+
// ignore read errors
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Unknown ecosystem — return empty strings so user fills them in
|
|
1398
|
+
return { test: '', lint: '', build: '', security: '' };
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Generate uds.project.yaml content from a commands map.
|
|
1403
|
+
* @param {{ test?: string, lint?: string, build?: string, security?: string }} commands
|
|
1404
|
+
* @returns {string}
|
|
1405
|
+
*/
|
|
1406
|
+
export function generateProjectConfigYaml(commands) {
|
|
1407
|
+
const lines = ['version: "1"', '', 'commands:'];
|
|
1408
|
+
for (const [intent, cmd] of Object.entries(commands)) {
|
|
1409
|
+
if (cmd && cmd.trim()) {
|
|
1410
|
+
lines.push(` ${intent}: ${cmd.trim()}`);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return lines.join('\n') + '\n';
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Wraps promptProjectCommandContract with an initial confirm prompt.
|
|
1418
|
+
* Used as the optional Step 13 in the init flow so the whole step can be
|
|
1419
|
+
* mocked in tests as a single unit.
|
|
1420
|
+
*
|
|
1421
|
+
* @param {string} projectPath - Absolute project root path
|
|
1422
|
+
* @returns {Promise<void>}
|
|
1423
|
+
*/
|
|
1424
|
+
export async function promptProjectContractStep(projectPath) {
|
|
1425
|
+
try {
|
|
1426
|
+
const wantContract = await inquirerConfirm({
|
|
1427
|
+
message: 'Create uds.project.yaml (project command contract)?',
|
|
1428
|
+
default: true
|
|
1429
|
+
});
|
|
1430
|
+
if (wantContract) {
|
|
1431
|
+
await promptProjectCommandContract(projectPath);
|
|
1432
|
+
}
|
|
1433
|
+
// If false — silently skip (caller can show a hint message)
|
|
1434
|
+
} catch {
|
|
1435
|
+
// Ctrl+C or any error — treat as skip
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Interactive wizard that guides the user through creating uds.project.yaml.
|
|
1441
|
+
* Detects the project ecosystem and pre-fills defaults.
|
|
1442
|
+
*
|
|
1443
|
+
* @param {string} projectPath - Absolute project root path
|
|
1444
|
+
* @returns {Promise<boolean>} true if the file was written, false if skipped
|
|
1445
|
+
*/
|
|
1446
|
+
export async function promptProjectCommandContract(projectPath) {
|
|
1447
|
+
const contractPath = join(projectPath, 'uds.project.yaml');
|
|
1448
|
+
|
|
1449
|
+
// If already exists, ask whether to overwrite
|
|
1450
|
+
if (existsSync(contractPath)) {
|
|
1451
|
+
console.log();
|
|
1452
|
+
console.log(chalk.yellow(' uds.project.yaml already exists.'));
|
|
1453
|
+
|
|
1454
|
+
const overwrite = await inquirerConfirm({
|
|
1455
|
+
message: 'Overwrite existing uds.project.yaml?',
|
|
1456
|
+
default: false
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
if (!overwrite) {
|
|
1460
|
+
console.log(chalk.gray(' Skipped — keeping existing uds.project.yaml'));
|
|
1461
|
+
return false;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
const suggestions = suggestCommands(projectPath);
|
|
1466
|
+
const hasAnySuggestion = Object.values(suggestions).some(v => v);
|
|
1467
|
+
|
|
1468
|
+
console.log();
|
|
1469
|
+
console.log(chalk.bold('📋 Project Command Contract (uds.project.yaml)'));
|
|
1470
|
+
console.log(chalk.gray(' Tell UDS how to run standard commands in this project (language-agnostic).'));
|
|
1471
|
+
console.log(chalk.gray(' Press Enter to accept a suggestion, or type your own. Leave blank to skip.'));
|
|
1472
|
+
console.log();
|
|
1473
|
+
|
|
1474
|
+
if (hasAnySuggestion) {
|
|
1475
|
+
console.log(chalk.gray(' Detected suggestions:'));
|
|
1476
|
+
for (const [intent, cmd] of Object.entries(suggestions)) {
|
|
1477
|
+
if (cmd) console.log(chalk.gray(` ${intent}: ${cmd}`));
|
|
1478
|
+
}
|
|
1479
|
+
console.log();
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const intents = ['test', 'lint', 'build', 'security'];
|
|
1483
|
+
const commands = {};
|
|
1484
|
+
|
|
1485
|
+
for (const intent of intents) {
|
|
1486
|
+
const defaultVal = suggestions[intent] || '';
|
|
1487
|
+
const answer = await input({
|
|
1488
|
+
message: ` ${intent} command${defaultVal ? ` (default: ${defaultVal})` : ' (optional)'}:`,
|
|
1489
|
+
default: defaultVal
|
|
1490
|
+
});
|
|
1491
|
+
commands[intent] = answer.trim();
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const hasAnyCommand = Object.values(commands).some(v => v);
|
|
1495
|
+
if (!hasAnyCommand) {
|
|
1496
|
+
console.log(chalk.gray(' No commands entered — uds.project.yaml not created.'));
|
|
1497
|
+
return false;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
const yaml = generateProjectConfigYaml(commands);
|
|
1501
|
+
writeFileSync(contractPath, yaml, 'utf-8');
|
|
1502
|
+
|
|
1503
|
+
console.log();
|
|
1504
|
+
console.log(chalk.green(' ✔ uds.project.yaml created'));
|
|
1505
|
+
console.log(chalk.gray(` Path: ${contractPath}`));
|
|
1506
|
+
return true;
|
|
1507
|
+
}
|
|
@@ -340,6 +340,30 @@ function scanHook(state, projectPath) {
|
|
|
340
340
|
} catch {
|
|
341
341
|
// Ignore read errors
|
|
342
342
|
}
|
|
343
|
+
|
|
344
|
+
// Also scan native .git/hooks/pre-commit (installed by uds init for non-Node projects)
|
|
345
|
+
const nativeHookPath = join(projectPath, '.git', 'hooks', 'pre-commit');
|
|
346
|
+
if (existsSync(nativeHookPath)) {
|
|
347
|
+
try {
|
|
348
|
+
const content = readFileSync(nativeHookPath, 'utf-8');
|
|
349
|
+
const udsLines = content.split('\n').filter(line =>
|
|
350
|
+
line.includes('uds') || line.includes('UDS') || line.includes('.standards')
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
if (udsLines.length > 0) {
|
|
354
|
+
state.hook.set('.git/hooks/pre-commit', {
|
|
355
|
+
relativePath: '.git/hooks/pre-commit',
|
|
356
|
+
hash: computeFileHash(join(projectPath, '.git', 'hooks', 'pre-commit'))?.hash || null,
|
|
357
|
+
size: null,
|
|
358
|
+
category: 'hook',
|
|
359
|
+
sourcePath: null,
|
|
360
|
+
metadata: { udsLines, scanned: true, type: 'native' }
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
} catch {
|
|
364
|
+
// Ignore read errors
|
|
365
|
+
}
|
|
366
|
+
}
|
|
343
367
|
}
|
|
344
368
|
|
|
345
369
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -39,5 +39,36 @@ export function uninstallHook(projectPath, options = {}) {
|
|
|
39
39
|
result.errors.push(`.husky/pre-commit — ${error.message}`);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// Also handle native .git/hooks/pre-commit (installed by uds init for non-Node projects)
|
|
43
|
+
const nativeHookPath = join(projectPath, '.git', 'hooks', 'pre-commit');
|
|
44
|
+
if (existsSync(nativeHookPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const content = readFileSync(nativeHookPath, 'utf-8');
|
|
47
|
+
const udsPattern = /uds\s+check|checkin-standards|UDS pre-commit hook/;
|
|
48
|
+
|
|
49
|
+
if (!udsPattern.test(content)) {
|
|
50
|
+
result.skipped.push('.git/hooks/pre-commit (no UDS lines found)');
|
|
51
|
+
} else if (dryRun) {
|
|
52
|
+
result.removed.push('.git/hooks/pre-commit (UDS native hook)');
|
|
53
|
+
} else {
|
|
54
|
+
// Remove only UDS-related lines, keep other hook content
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
const filtered = lines.filter(line => !udsPattern.test(line));
|
|
57
|
+
|
|
58
|
+
// If only shebang remains, remove the file entirely; otherwise rewrite
|
|
59
|
+
const nonEmpty = filtered.filter(l => l.trim() && l.trim() !== '#!/bin/sh');
|
|
60
|
+
if (nonEmpty.length === 0) {
|
|
61
|
+
unlinkSync(nativeHookPath);
|
|
62
|
+
result.removed.push('.git/hooks/pre-commit (UDS native hook, file removed)');
|
|
63
|
+
} else {
|
|
64
|
+
writeFileSync(nativeHookPath, filtered.join('\n'), 'utf-8');
|
|
65
|
+
result.removed.push('.git/hooks/pre-commit (UDS lines removed)');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
result.errors.push(`.git/hooks/pre-commit — ${error.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
42
73
|
return result;
|
|
43
74
|
}
|
|
@@ -192,6 +192,22 @@ export function analyzeExistingPatterns(e2eDir) {
|
|
|
192
192
|
// REQ-4: E2E 測試骨架生成
|
|
193
193
|
// ============================================================
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Detect the project ecosystem based on manifest files
|
|
197
|
+
* @param {string} [projectPath='.'] - Path to the project root
|
|
198
|
+
* @returns {'node'|'python'|'go'|'java'|'rust'|'ruby'}
|
|
199
|
+
*/
|
|
200
|
+
function detectEcosystem(projectPath = '.') {
|
|
201
|
+
const p = (f) => join(projectPath, f);
|
|
202
|
+
if (existsSync(p('package.json'))) return 'node';
|
|
203
|
+
if (existsSync(p('requirements.txt')) || existsSync(p('pyproject.toml'))) return 'python';
|
|
204
|
+
if (existsSync(p('go.mod'))) return 'go';
|
|
205
|
+
if (existsSync(p('pom.xml')) || existsSync(p('build.gradle'))) return 'java';
|
|
206
|
+
if (existsSync(p('Cargo.toml'))) return 'rust';
|
|
207
|
+
if (existsSync(p('Gemfile'))) return 'ruby';
|
|
208
|
+
return 'node'; // fallback
|
|
209
|
+
}
|
|
210
|
+
|
|
195
211
|
const SKELETON_TEMPLATES = {
|
|
196
212
|
vitest: {
|
|
197
213
|
header: (specId) => `/**
|
|
@@ -298,6 +314,65 @@ beforeEach(() => {
|
|
|
298
314
|
// [TODO] Verify expected outcomes
|
|
299
315
|
// cy.contains('expected text').should('be.visible');
|
|
300
316
|
});
|
|
317
|
+
`
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// Python (pytest-playwright)
|
|
321
|
+
'pytest-playwright': {
|
|
322
|
+
header: (specId) => `# [Generated] E2E tests for ${specId}
|
|
323
|
+
# [TODO] Fill in test implementations
|
|
324
|
+
|
|
325
|
+
import pytest
|
|
326
|
+
from playwright.sync_api import Page
|
|
327
|
+
`,
|
|
328
|
+
setup: () => `
|
|
329
|
+
# [TODO] Setup fixtures and test data
|
|
330
|
+
`,
|
|
331
|
+
test: (scenario) => `
|
|
332
|
+
# @${scenario.specId} @${scenario.ac}
|
|
333
|
+
def test_${scenario.name.toLowerCase().replace(/\s+/g, '_')}(page: Page):
|
|
334
|
+
# Arrange
|
|
335
|
+
# [TODO] Setup test preconditions
|
|
336
|
+
|
|
337
|
+
# Act
|
|
338
|
+
# [TODO] Execute the user flow
|
|
339
|
+
|
|
340
|
+
# Assert
|
|
341
|
+
# [TODO] Verify expected outcomes
|
|
342
|
+
`
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
// Go (chromedp)
|
|
346
|
+
chromedp: {
|
|
347
|
+
header: (specId) => `// [Generated] E2E tests for ${specId}
|
|
348
|
+
// [TODO] Fill in test implementations
|
|
349
|
+
|
|
350
|
+
package e2e_test
|
|
351
|
+
|
|
352
|
+
import (
|
|
353
|
+
\t"context"
|
|
354
|
+
\t"testing"
|
|
355
|
+
\t"github.com/chromedp/chromedp"
|
|
356
|
+
)
|
|
357
|
+
`,
|
|
358
|
+
setup: () => `
|
|
359
|
+
// [TODO] Setup shared test context if needed
|
|
360
|
+
`,
|
|
361
|
+
test: (scenario) => `
|
|
362
|
+
// @${scenario.specId} @${scenario.ac}
|
|
363
|
+
func Test${scenario.name.replace(/\s+/g, '')}(t *testing.T) {
|
|
364
|
+
\tctx, cancel := chromedp.NewContext(context.Background())
|
|
365
|
+
\tdefer cancel()
|
|
366
|
+
|
|
367
|
+
\t// Arrange
|
|
368
|
+
\t// [TODO] Setup test preconditions
|
|
369
|
+
|
|
370
|
+
\t// Act
|
|
371
|
+
\t// [TODO] Add chromedp tasks
|
|
372
|
+
|
|
373
|
+
\t// Assert
|
|
374
|
+
\t// [TODO] Verify expected outcomes
|
|
375
|
+
}
|
|
301
376
|
`
|
|
302
377
|
}
|
|
303
378
|
};
|
|
@@ -308,16 +383,24 @@ beforeEach(() => {
|
|
|
308
383
|
* @param {{ framework: string }} options
|
|
309
384
|
* @returns {string}
|
|
310
385
|
*/
|
|
311
|
-
export function generateE2eSkeleton(scenarios, { framework = 'vitest' } = {}) {
|
|
312
|
-
|
|
386
|
+
export function generateE2eSkeleton(scenarios, { framework = 'vitest', projectPath } = {}) {
|
|
387
|
+
// If no framework is explicitly specified (using default), detect ecosystem and adjust
|
|
388
|
+
const resolvedFramework = (() => {
|
|
389
|
+
if (framework !== 'vitest') return framework; // caller specified explicitly
|
|
390
|
+
const ecosystem = detectEcosystem(projectPath);
|
|
391
|
+
if (ecosystem === 'python') return 'pytest-playwright';
|
|
392
|
+
if (ecosystem === 'go') return 'chromedp';
|
|
393
|
+
return framework;
|
|
394
|
+
})();
|
|
395
|
+
const template = SKELETON_TEMPLATES[resolvedFramework] || SKELETON_TEMPLATES.vitest;
|
|
313
396
|
const specId = scenarios[0]?.specId || 'SPEC-XXX';
|
|
314
397
|
|
|
315
398
|
let output = template.header(specId);
|
|
316
399
|
output += template.setup();
|
|
317
400
|
|
|
318
|
-
if (
|
|
401
|
+
if (resolvedFramework === 'vitest') {
|
|
319
402
|
output += `\ndescribe('E2E: ${specId}', () => {`;
|
|
320
|
-
} else if (
|
|
403
|
+
} else if (resolvedFramework === 'cypress') {
|
|
321
404
|
output += `\ndescribe('E2E: ${specId}', () => {`;
|
|
322
405
|
}
|
|
323
406
|
|
|
@@ -325,7 +408,7 @@ export function generateE2eSkeleton(scenarios, { framework = 'vitest' } = {}) {
|
|
|
325
408
|
output += template.test(s);
|
|
326
409
|
}
|
|
327
410
|
|
|
328
|
-
if (
|
|
411
|
+
if (resolvedFramework === 'vitest' || resolvedFramework === 'cypress') {
|
|
329
412
|
output += '});\n';
|
|
330
413
|
}
|
|
331
414
|
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
|
|
4
|
-
const SUPPORTED_FRAMEWORKS = [
|
|
4
|
+
const SUPPORTED_FRAMEWORKS = [
|
|
5
|
+
// JavaScript/TypeScript
|
|
6
|
+
'playwright', 'cypress', 'vitest', 'webdriverio',
|
|
7
|
+
// Python
|
|
8
|
+
'pytest-playwright', 'selenium-python', 'robot-framework', 'behave',
|
|
9
|
+
// Java
|
|
10
|
+
'selenium-java', 'playwright-java', 'gauge',
|
|
11
|
+
// Go
|
|
12
|
+
'chromedp', 'rod',
|
|
13
|
+
// C#
|
|
14
|
+
'playwright-dotnet', 'selenium-dotnet',
|
|
15
|
+
// Ruby
|
|
16
|
+
'capybara', 'watir',
|
|
17
|
+
];
|
|
5
18
|
|
|
6
19
|
/**
|
|
7
20
|
* Detect E2E testing frameworks in the project
|
|
@@ -11,6 +24,65 @@ const SUPPORTED_FRAMEWORKS = ['playwright', 'cypress', 'vitest'];
|
|
|
11
24
|
export function detectE2eFramework(projectPath) {
|
|
12
25
|
const detected = [];
|
|
13
26
|
|
|
27
|
+
// Python ecosystem detection
|
|
28
|
+
const pythonFiles = ['requirements.txt', 'pyproject.toml'];
|
|
29
|
+
for (const pyFile of pythonFiles) {
|
|
30
|
+
const pyPath = join(projectPath, pyFile);
|
|
31
|
+
if (existsSync(pyPath)) {
|
|
32
|
+
try {
|
|
33
|
+
const content = readFileSync(pyPath, 'utf-8');
|
|
34
|
+
if (content.includes('pytest-playwright')) detected.push('pytest-playwright');
|
|
35
|
+
if (content.includes('selenium')) detected.push('selenium-python');
|
|
36
|
+
if (content.includes('robotframework')) detected.push('robot-framework');
|
|
37
|
+
if (content.includes('behave')) detected.push('behave');
|
|
38
|
+
} catch {
|
|
39
|
+
// Ignore read errors
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Go ecosystem detection
|
|
46
|
+
const goModPath = join(projectPath, 'go.mod');
|
|
47
|
+
if (existsSync(goModPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const content = readFileSync(goModPath, 'utf-8');
|
|
50
|
+
if (content.includes('chromedp')) detected.push('chromedp');
|
|
51
|
+
if (content.includes('/rod')) detected.push('rod');
|
|
52
|
+
} catch {
|
|
53
|
+
// Ignore read errors
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Java ecosystem detection
|
|
58
|
+
for (const javaFile of ['pom.xml', 'build.gradle']) {
|
|
59
|
+
const javaPath = join(projectPath, javaFile);
|
|
60
|
+
if (existsSync(javaPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const content = readFileSync(javaPath, 'utf-8');
|
|
63
|
+
if (content.includes('selenium')) detected.push('selenium-java');
|
|
64
|
+
if (content.includes('playwright')) detected.push('playwright-java');
|
|
65
|
+
if (content.includes('gauge')) detected.push('gauge');
|
|
66
|
+
} catch {
|
|
67
|
+
// Ignore read errors
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ruby ecosystem detection
|
|
74
|
+
const gemfilePath = join(projectPath, 'Gemfile');
|
|
75
|
+
if (existsSync(gemfilePath)) {
|
|
76
|
+
try {
|
|
77
|
+
const content = readFileSync(gemfilePath, 'utf-8');
|
|
78
|
+
if (content.includes('capybara')) detected.push('capybara');
|
|
79
|
+
if (content.includes('watir')) detected.push('watir');
|
|
80
|
+
} catch {
|
|
81
|
+
// Ignore read errors
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// JavaScript/TypeScript ecosystem detection
|
|
14
86
|
const packagePath = join(projectPath, 'package.json');
|
|
15
87
|
if (existsSync(packagePath)) {
|
|
16
88
|
try {
|
|
@@ -3097,6 +3097,24 @@ export function getToolFilePath(tool) {
|
|
|
3097
3097
|
|
|
3098
3098
|
|
|
3099
3099
|
|
|
3100
|
+
/**
|
|
3101
|
+
* Get default commands based on ecosystem
|
|
3102
|
+
* @param {'node'|'python'|'go'|'java_maven'|'java_gradle'|'rust'|'ruby'} ecosystem
|
|
3103
|
+
* @returns {{ test: string, lint: string, build: string, install: string }}
|
|
3104
|
+
*/
|
|
3105
|
+
function getDefaultCommands(ecosystem) {
|
|
3106
|
+
const commands = {
|
|
3107
|
+
node: { test: 'npm test', lint: 'npm run lint', build: 'npm run build', install: 'npm install' },
|
|
3108
|
+
python: { test: 'python -m pytest', lint: 'python -m ruff check .', build: 'python -m build', install: 'pip install -r requirements.txt' },
|
|
3109
|
+
go: { test: 'go test ./...', lint: 'go vet ./...', build: 'go build ./...', install: 'go mod download' },
|
|
3110
|
+
java_maven: { test: 'mvn test', lint: 'mvn checkstyle:check', build: 'mvn package', install: 'mvn dependency:resolve' },
|
|
3111
|
+
java_gradle: { test: './gradlew test', lint: './gradlew checkstyleMain', build: './gradlew build', install: './gradlew dependencies' },
|
|
3112
|
+
rust: { test: 'cargo test', lint: 'cargo clippy', build: 'cargo build', install: 'cargo fetch' },
|
|
3113
|
+
ruby: { test: 'bundle exec rspec', lint: 'bundle exec rubocop', build: 'gem build *.gemspec', install: 'bundle install' },
|
|
3114
|
+
};
|
|
3115
|
+
return commands[ecosystem] || commands.node;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3100
3118
|
/**
|
|
3101
3119
|
* Detect build/test commands based on project files
|
|
3102
3120
|
* @param {string} [projectPath] - Project root path
|
|
@@ -3104,11 +3122,12 @@ export function getToolFilePath(tool) {
|
|
|
3104
3122
|
*/
|
|
3105
3123
|
function detectBuildCommands(projectPath) {
|
|
3106
3124
|
if (!projectPath) {
|
|
3125
|
+
const defaultCmds = getDefaultCommands('node');
|
|
3107
3126
|
return [
|
|
3108
3127
|
'# Check project configuration for build commands',
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3128
|
+
`${defaultCmds.install} # Install dependencies (Node.js)`,
|
|
3129
|
+
`${defaultCmds.test} # Run tests`,
|
|
3130
|
+
`${defaultCmds.lint} # Check code style`
|
|
3112
3131
|
];
|
|
3113
3132
|
}
|
|
3114
3133
|
|