@specsafe/cli 0.6.0 → 0.7.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/dist/commands/capsule.d.ts +8 -0
- package/dist/commands/capsule.d.ts.map +1 -0
- package/dist/commands/capsule.js +466 -0
- package/dist/commands/capsule.js.map +1 -0
- package/dist/commands/create.d.ts +10 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +120 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/export.d.ts +7 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +179 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/memory.d.ts +3 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +166 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +65 -1
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/shard.d.ts +6 -0
- package/dist/commands/shard.d.ts.map +1 -0
- package/dist/commands/shard.js +199 -0
- package/dist/commands/shard.js.map +1 -0
- package/dist/commands/test-guide.d.ts +3 -0
- package/dist/commands/test-guide.d.ts.map +1 -0
- package/dist/commands/test-guide.js +190 -0
- package/dist/commands/test-guide.js.map +1 -0
- package/dist/commands/test-report.d.ts +3 -0
- package/dist/commands/test-report.d.ts.map +1 -0
- package/dist/commands/test-report.js +196 -0
- package/dist/commands/test-report.js.map +1 -0
- package/dist/commands/test-submit.d.ts +6 -0
- package/dist/commands/test-submit.d.ts.map +1 -0
- package/dist/commands/test-submit.js +236 -0
- package/dist/commands/test-submit.js.map +1 -0
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shard Command - Analyze and split specs into AI-consumable chunks
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
7
|
+
import { resolve, basename } from 'path';
|
|
8
|
+
import { ShardEngine } from '@specsafe/core';
|
|
9
|
+
const VALID_STRATEGIES = new Set([
|
|
10
|
+
'by-section',
|
|
11
|
+
'by-requirement',
|
|
12
|
+
'by-scenario',
|
|
13
|
+
'auto',
|
|
14
|
+
]);
|
|
15
|
+
const DEFAULT_MAX_TOKENS = 2000;
|
|
16
|
+
function parseStrategy(input) {
|
|
17
|
+
if (!input) {
|
|
18
|
+
return 'auto';
|
|
19
|
+
}
|
|
20
|
+
if (!VALID_STRATEGIES.has(input)) {
|
|
21
|
+
throw new Error(`Invalid strategy "${input}". Expected one of: ${Array.from(VALID_STRATEGIES).join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
return input;
|
|
24
|
+
}
|
|
25
|
+
function parseMaxTokens(input) {
|
|
26
|
+
const value = Number.parseInt(input, 10);
|
|
27
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
28
|
+
throw new Error(`Invalid --max-tokens value "${input}". It must be a positive integer.`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function sanitizeFileSegment(value) {
|
|
33
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
34
|
+
}
|
|
35
|
+
export const shardCommand = new Command('shard')
|
|
36
|
+
.description('Analyze and split specs into AI-consumable chunks')
|
|
37
|
+
.argument('<spec>', 'Path to the spec file')
|
|
38
|
+
.option('-s, --strategy <strategy>', 'Sharding strategy (by-section|by-requirement|by-scenario|auto)')
|
|
39
|
+
.option('-m, --max-tokens <number>', 'Maximum tokens per shard for auto strategy', String(DEFAULT_MAX_TOKENS))
|
|
40
|
+
.option('-o, --output <dir>', 'Output directory for shard files')
|
|
41
|
+
.option('--no-preserve-context', 'Do not preserve context across shards')
|
|
42
|
+
.option('--no-include-metadata', 'Do not include metadata in shards')
|
|
43
|
+
.option('--json', 'Output as JSON')
|
|
44
|
+
.action(async (specPath, options) => {
|
|
45
|
+
try {
|
|
46
|
+
const resolvedPath = resolve(process.cwd(), specPath);
|
|
47
|
+
if (!existsSync(resolvedPath)) {
|
|
48
|
+
console.error(chalk.red(`Error: Spec file not found: ${specPath}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const specContent = readFileSync(resolvedPath, 'utf-8');
|
|
52
|
+
const strategy = parseStrategy(options.strategy);
|
|
53
|
+
const maxTokensPerShard = parseMaxTokens(options.maxTokens);
|
|
54
|
+
const preserveContext = options.preserveContext !== false;
|
|
55
|
+
const includeMetadata = options.includeMetadata !== false;
|
|
56
|
+
const shardOptions = {
|
|
57
|
+
strategy,
|
|
58
|
+
maxTokensPerShard,
|
|
59
|
+
preserveContext,
|
|
60
|
+
includeMetadata,
|
|
61
|
+
};
|
|
62
|
+
const engine = new ShardEngine(shardOptions);
|
|
63
|
+
const result = engine.shard(specContent, shardOptions);
|
|
64
|
+
if (!result.success) {
|
|
65
|
+
console.error(chalk.red('Error sharding spec:'), result.error);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const { plan, durationMs } = result;
|
|
69
|
+
if (options.json) {
|
|
70
|
+
console.log(JSON.stringify({
|
|
71
|
+
success: true,
|
|
72
|
+
durationMs,
|
|
73
|
+
plan: {
|
|
74
|
+
...plan,
|
|
75
|
+
shards: plan.shards.map(s => ({
|
|
76
|
+
id: s.id,
|
|
77
|
+
type: s.type,
|
|
78
|
+
tokenCount: s.tokenCount,
|
|
79
|
+
priority: s.priority,
|
|
80
|
+
sectionName: s.sectionName,
|
|
81
|
+
})),
|
|
82
|
+
},
|
|
83
|
+
}, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
displayAnalysis(plan);
|
|
87
|
+
displayShardPlan(plan);
|
|
88
|
+
if (options.output) {
|
|
89
|
+
await writeShards(plan, options.output, specPath);
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.gray(`\nCompleted in ${durationMs}ms`));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
console.error(chalk.red('Error:'), message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Display analysis results
|
|
101
|
+
*/
|
|
102
|
+
function displayAnalysis(plan) {
|
|
103
|
+
const { analysis } = plan;
|
|
104
|
+
console.log(chalk.bold.blue(`\n📊 Spec Analysis\n`));
|
|
105
|
+
console.log(chalk.gray('Complexity:'), chalk.cyan(`${analysis.complexity}/100`));
|
|
106
|
+
console.log(chalk.gray('Total Tokens:'), chalk.cyan(analysis.totalTokens.toLocaleString()));
|
|
107
|
+
console.log(chalk.gray('Sections:'), chalk.cyan(analysis.sectionCount));
|
|
108
|
+
console.log(chalk.gray('Requirements:'), chalk.cyan(analysis.requirementCount));
|
|
109
|
+
console.log(chalk.gray('Scenarios:'), chalk.cyan(analysis.scenarioCount));
|
|
110
|
+
console.log(chalk.gray('\nRecommended Strategy:'), chalk.green(analysis.recommendedStrategy));
|
|
111
|
+
console.log(chalk.gray(analysis.recommendationReason));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Display shard plan
|
|
115
|
+
*/
|
|
116
|
+
function displayShardPlan(plan) {
|
|
117
|
+
console.log(chalk.bold.blue(`\n📝 Shard Plan\n`));
|
|
118
|
+
console.log(chalk.gray('Total Shards:'), chalk.cyan(plan.shards.length));
|
|
119
|
+
console.log(chalk.gray('Estimated Tokens:'), chalk.cyan(plan.estimatedTokens.toLocaleString()));
|
|
120
|
+
if (plan.crossReferences.length > 0) {
|
|
121
|
+
console.log(chalk.gray('Cross-References:'), chalk.cyan(plan.crossReferences.length));
|
|
122
|
+
}
|
|
123
|
+
console.log(chalk.bold('\nShards:'));
|
|
124
|
+
for (const shard of plan.shards) {
|
|
125
|
+
const tokens = shard.tokenCount || 0;
|
|
126
|
+
const typeColor = shard.type === 'metadata'
|
|
127
|
+
? chalk.gray
|
|
128
|
+
: shard.type === 'requirement'
|
|
129
|
+
? chalk.yellow
|
|
130
|
+
: shard.type === 'scenario'
|
|
131
|
+
? chalk.blue
|
|
132
|
+
: chalk.white;
|
|
133
|
+
console.log(` ${chalk.cyan(shard.id.padEnd(20))} ${typeColor(shard.type.padEnd(12))} ${tokens
|
|
134
|
+
.toString()
|
|
135
|
+
.padStart(6)} tokens${shard.sectionName ? ` - ${chalk.gray(shard.sectionName)}` : ''}`);
|
|
136
|
+
if (shard.dependencies.length > 0) {
|
|
137
|
+
console.log(` ${chalk.gray('└─ Dependencies:')} ${shard.dependencies.map(d => chalk.gray(d)).join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (plan.recommendedOrder.length > 0 && plan.recommendedOrder.length !== plan.shards.length) {
|
|
141
|
+
console.log(chalk.bold('\nRecommended Processing Order:'));
|
|
142
|
+
console.log(` ${plan.recommendedOrder.map(id => chalk.cyan(id)).join(' → ')}`);
|
|
143
|
+
}
|
|
144
|
+
if (plan.crossReferences.length > 0) {
|
|
145
|
+
console.log(chalk.bold('\nCross-References:'));
|
|
146
|
+
for (const ref of plan.crossReferences.slice(0, 10)) {
|
|
147
|
+
console.log(` ${chalk.cyan(ref.from)} ${chalk.gray('→')} ${chalk.cyan(ref.to)} (${chalk.yellow(ref.type)})`);
|
|
148
|
+
}
|
|
149
|
+
if (plan.crossReferences.length > 10) {
|
|
150
|
+
console.log(` ${chalk.gray(`... and ${plan.crossReferences.length - 10} more`)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Write shards to files
|
|
156
|
+
*/
|
|
157
|
+
async function writeShards(plan, outputDir, specPath) {
|
|
158
|
+
const resolvedDir = resolve(process.cwd(), outputDir);
|
|
159
|
+
if (!existsSync(resolvedDir)) {
|
|
160
|
+
mkdirSync(resolvedDir, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
const baseName = sanitizeFileSegment(basename(specPath).replace(/\.(md|txt|spec)$/i, '')) || 'spec';
|
|
163
|
+
console.log(chalk.bold.blue(`\n💾 Writing Shards\n`));
|
|
164
|
+
console.log(chalk.gray(`Output directory: ${resolvedDir}\n`));
|
|
165
|
+
for (const shard of plan.shards) {
|
|
166
|
+
const safeShardId = sanitizeFileSegment(shard.id);
|
|
167
|
+
const fileName = `${baseName}-${safeShardId}.md`;
|
|
168
|
+
const filePath = resolve(resolvedDir, fileName);
|
|
169
|
+
let content = shard.content;
|
|
170
|
+
if (shard.tokenCount && shard.type !== 'metadata') {
|
|
171
|
+
content =
|
|
172
|
+
`<!--\nShard: ${shard.id}\nType: ${shard.type}\nTokens: ${shard.tokenCount}\nPriority: ${shard.priority}\n` +
|
|
173
|
+
(shard.sectionName ? `Section: ${shard.sectionName}\n` : '') +
|
|
174
|
+
(shard.dependencies.length > 0 ? `Dependencies: ${shard.dependencies.join(', ')}\n` : '') +
|
|
175
|
+
'-->\n\n' +
|
|
176
|
+
content;
|
|
177
|
+
}
|
|
178
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
179
|
+
console.log(chalk.green('✓') + ` ${fileName}`);
|
|
180
|
+
}
|
|
181
|
+
const summaryPath = resolve(resolvedDir, `${baseName}-plan.json`);
|
|
182
|
+
const summary = {
|
|
183
|
+
specPath,
|
|
184
|
+
generatedAt: new Date().toISOString(),
|
|
185
|
+
plan: {
|
|
186
|
+
...plan,
|
|
187
|
+
shards: plan.shards.map((s) => ({
|
|
188
|
+
id: s.id,
|
|
189
|
+
type: s.type,
|
|
190
|
+
tokenCount: s.tokenCount,
|
|
191
|
+
priority: s.priority,
|
|
192
|
+
sectionName: s.sectionName,
|
|
193
|
+
})),
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
writeFileSync(summaryPath, JSON.stringify(summary, null, 2), 'utf-8');
|
|
197
|
+
console.log(chalk.green('✓') + ` ${baseName}-plan.json`);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=shard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shard.js","sourceRoot":"","sources":["../../src/commands/shard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-guide.d.ts","sourceRoot":"","sources":["../../src/commands/test-guide.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,eAAO,MAAM,gBAAgB,SAuHzB,CAAC"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { Workflow, ProjectTracker, generateGuide, generateGuideContent, convertToPlaywrightScenarios, generatePlaywrightScript, } from '@specsafe/core';
|
|
5
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
6
|
+
import { join, dirname, normalize, isAbsolute } from 'path';
|
|
7
|
+
function validateSpecInput(specInput) {
|
|
8
|
+
if (specInput.endsWith('.md'))
|
|
9
|
+
return specInput;
|
|
10
|
+
if (!/^[A-Za-z0-9_-]+$/.test(specInput)) {
|
|
11
|
+
throw new Error('Invalid spec identifier. Use only letters, numbers, dash, and underscore.');
|
|
12
|
+
}
|
|
13
|
+
return specInput;
|
|
14
|
+
}
|
|
15
|
+
function sanitizeOutputPath(outputPath) {
|
|
16
|
+
const normalized = normalize(outputPath);
|
|
17
|
+
if (isAbsolute(normalized) || normalized.includes('..')) {
|
|
18
|
+
throw new Error('Output path must be a safe relative path');
|
|
19
|
+
}
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
export const testGuideCommand = new Command('test-guide')
|
|
23
|
+
.description('Generate E2E test guide from spec')
|
|
24
|
+
.argument('<spec>', 'Spec ID or path to spec file')
|
|
25
|
+
.option('-o, --output <file>', 'Output file path')
|
|
26
|
+
.option('--format <format>', 'Output format (markdown|json)', 'markdown')
|
|
27
|
+
.option('--mode <mode>', 'Automation mode (manual|playwright)', 'manual')
|
|
28
|
+
.option('--auto', 'Generate Playwright script output')
|
|
29
|
+
.option('--priority <priorities...>', 'Filter by priority (P0 P1 P2)', ['P0', 'P1', 'P2'])
|
|
30
|
+
.action(async (specInput, options) => {
|
|
31
|
+
const spinner = ora('Generating test guide...').start();
|
|
32
|
+
try {
|
|
33
|
+
specInput = validateSpecInput(specInput);
|
|
34
|
+
const workflow = new Workflow();
|
|
35
|
+
const tracker = new ProjectTracker(process.cwd());
|
|
36
|
+
await tracker.loadSpecsIntoWorkflow(workflow);
|
|
37
|
+
let specId;
|
|
38
|
+
let specContent;
|
|
39
|
+
let specPath;
|
|
40
|
+
if (specInput.endsWith('.md')) {
|
|
41
|
+
specPath = specInput;
|
|
42
|
+
try {
|
|
43
|
+
specContent = await readFile(specPath, 'utf-8');
|
|
44
|
+
specId = specInput.split('/').pop().replace('.md', '');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`Spec file not found: ${specPath}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
specId = specInput;
|
|
52
|
+
specPath = join('specs/active', `${specId}.md`);
|
|
53
|
+
try {
|
|
54
|
+
specContent = await readFile(specPath, 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
throw new Error(`Spec file not found: ${specPath}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let spec = workflow.getSpec(specId);
|
|
61
|
+
if (!spec)
|
|
62
|
+
spec = parseSpecFromContent(specId, specContent);
|
|
63
|
+
spinner.text = 'Generating test content...';
|
|
64
|
+
const priorityFilter = options.priority;
|
|
65
|
+
const mode = options.auto ? 'playwright' : (options.mode ?? 'manual');
|
|
66
|
+
const guide = generateGuide(spec, {
|
|
67
|
+
priorityFilter,
|
|
68
|
+
format: options.format,
|
|
69
|
+
mode,
|
|
70
|
+
});
|
|
71
|
+
let output;
|
|
72
|
+
let outputFormat;
|
|
73
|
+
if (options.auto) {
|
|
74
|
+
const scenarios = convertToPlaywrightScenarios(guide);
|
|
75
|
+
output = generatePlaywrightScript(spec.id, scenarios);
|
|
76
|
+
outputFormat = 'js';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const result = generateGuideContent(spec, {
|
|
80
|
+
priorityFilter,
|
|
81
|
+
format: options.format,
|
|
82
|
+
mode,
|
|
83
|
+
});
|
|
84
|
+
output = result.content;
|
|
85
|
+
outputFormat = result.format === 'markdown' ? 'md' : result.format;
|
|
86
|
+
}
|
|
87
|
+
spinner.stop();
|
|
88
|
+
let outputPath;
|
|
89
|
+
if (options.output) {
|
|
90
|
+
outputPath = sanitizeOutputPath(options.output);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const guidesDir = join('.specsafe', 'e2e', 'guides');
|
|
94
|
+
await mkdir(guidesDir, { recursive: true });
|
|
95
|
+
const extension = outputFormat;
|
|
96
|
+
outputPath = join(guidesDir, `test-guide-${specId}.${extension}`);
|
|
97
|
+
}
|
|
98
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
99
|
+
await writeFile(outputPath, output, 'utf-8');
|
|
100
|
+
console.log(chalk.green(`\n✅ Test guide generated: ${outputPath}`));
|
|
101
|
+
console.log(chalk.blue('\nGuide Summary:'));
|
|
102
|
+
console.log(` Mode: ${mode}`);
|
|
103
|
+
console.log(` Scenarios: ${guide.scenarios.length}`);
|
|
104
|
+
console.log(` Steps: ${guide.scenarios.reduce((sum, s) => sum + s.steps.length, 0)}`);
|
|
105
|
+
console.log(` P0 (Critical): ${guide.scenarios.filter(s => s.priority === 'P0').length}`);
|
|
106
|
+
console.log(` P1 (High): ${guide.scenarios.filter(s => s.priority === 'P1').length}`);
|
|
107
|
+
console.log(` P2 (Normal): ${guide.scenarios.filter(s => s.priority === 'P2').length}`);
|
|
108
|
+
console.log(chalk.blue('\nNext steps:'));
|
|
109
|
+
if (mode === 'playwright') {
|
|
110
|
+
console.log(chalk.gray(` 1. Run generated script with: npx playwright test ${outputPath}`));
|
|
111
|
+
console.log(chalk.gray(' 2. Review screenshots captured at configured intervals'));
|
|
112
|
+
console.log(chalk.gray(' 3. Validate assertions and adjust selectors as needed'));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
console.log(chalk.gray(` 1. Review the test guide: ${outputPath}`));
|
|
116
|
+
console.log(chalk.gray(' 2. Execute test scenarios manually'));
|
|
117
|
+
console.log(chalk.gray(' 3. Take screenshots at key steps'));
|
|
118
|
+
console.log(chalk.gray(` 4. Submit for analysis: specsafe test-submit ${specId} --screenshots ./my-screenshots/`));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
spinner.fail(chalk.red(error.message));
|
|
123
|
+
if (error.message.includes('not found')) {
|
|
124
|
+
console.log(chalk.gray(`\n💡 Tip: Create a spec first with 'specsafe new <name>'`));
|
|
125
|
+
}
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
function parseSpecFromContent(specId, content) {
|
|
130
|
+
const safeContent = content.slice(0, 500_000);
|
|
131
|
+
const nameMatch = safeContent.match(/^#\s+(.+?)(?:\s+Specification)?$/m);
|
|
132
|
+
const name = nameMatch ? nameMatch[1] : specId;
|
|
133
|
+
const descMatch = safeContent.match(/## Overview\n+([\s\S]*?)(?=##|$)/);
|
|
134
|
+
const description = descMatch ? descMatch[1].trim() : '';
|
|
135
|
+
const requirements = [];
|
|
136
|
+
const tableMatch = safeContent.match(/\| ID \| Requirement \| Priority \|[^|]+\|\n\|[-\s|]+\|\n([\s\S]*?)(?=\n## |\n### |$)/);
|
|
137
|
+
if (tableMatch) {
|
|
138
|
+
const rows = tableMatch[1].trim().split('\n');
|
|
139
|
+
for (const row of rows) {
|
|
140
|
+
const cells = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
141
|
+
if (cells.length >= 3) {
|
|
142
|
+
const [id, text, priority] = cells;
|
|
143
|
+
requirements.push({ id, text, priority: priority || 'P1', scenarios: [] });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const scenariosMatch = safeContent.match(/## \d+\.\s*Scenarios\n+([\s\S]*?)(?=## \d+\.|$)/);
|
|
148
|
+
if (scenariosMatch) {
|
|
149
|
+
const scenarioBlocks = scenariosMatch[1].split(/### Scenario \d+:/);
|
|
150
|
+
for (let i = 0; i < scenarioBlocks.length; i++) {
|
|
151
|
+
const block = scenarioBlocks[i];
|
|
152
|
+
const givenMatch = block.match(/[-*]\s*\*\*Given\*\*\s*(.+)/i);
|
|
153
|
+
const whenMatch = block.match(/[-*]\s*\*\*When\*\*\s*(.+)/i);
|
|
154
|
+
const thenMatch = block.match(/[-*]\s*\*\*Then\*\*\s*(.+)/i);
|
|
155
|
+
if (givenMatch && whenMatch && thenMatch) {
|
|
156
|
+
const scenario = {
|
|
157
|
+
id: `SC-${i + 1}`,
|
|
158
|
+
given: givenMatch[1].trim(),
|
|
159
|
+
when: whenMatch[1].trim(),
|
|
160
|
+
thenOutcome: thenMatch[1].trim(),
|
|
161
|
+
};
|
|
162
|
+
if (requirements.length > 0)
|
|
163
|
+
requirements[0].scenarios.push(scenario);
|
|
164
|
+
else
|
|
165
|
+
requirements.push({ id: 'FR-1', text: 'Generated from scenarios', priority: 'P1', scenarios: [scenario] });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (requirements.length === 0) {
|
|
170
|
+
requirements.push({
|
|
171
|
+
id: 'FR-1',
|
|
172
|
+
text: 'Feature implementation matches spec',
|
|
173
|
+
priority: 'P0',
|
|
174
|
+
scenarios: [{ id: 'SC-1', given: 'user accesses the feature', when: 'user interacts with the feature', thenOutcome: 'feature behaves as specified' }],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
id: specId,
|
|
179
|
+
name,
|
|
180
|
+
description,
|
|
181
|
+
stage: 'spec',
|
|
182
|
+
createdAt: new Date(),
|
|
183
|
+
updatedAt: new Date(),
|
|
184
|
+
requirements,
|
|
185
|
+
testFiles: [],
|
|
186
|
+
implementationFiles: [],
|
|
187
|
+
metadata: { author: 'unknown', project: 'unknown', tags: [] },
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=test-guide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-guide.js","sourceRoot":"","sources":["../../src/commands/test-guide.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-report.d.ts","sourceRoot":"","sources":["../../src/commands/test-report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,eAAO,MAAM,iBAAiB,SA0F1B,CAAC"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readFile, readdir } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
function validateSpecId(specId) {
|
|
7
|
+
if (!/^[A-Za-z0-9_-]+$/.test(specId)) {
|
|
8
|
+
throw new Error('Invalid spec ID. Use only letters, numbers, dash, and underscore.');
|
|
9
|
+
}
|
|
10
|
+
return specId;
|
|
11
|
+
}
|
|
12
|
+
export const testReportCommand = new Command('test-report')
|
|
13
|
+
.description('View E2E test report for a spec')
|
|
14
|
+
.argument('<spec>', 'Spec ID')
|
|
15
|
+
.option('-l, --latest', 'Show only the latest report', true)
|
|
16
|
+
.option('--json', 'Output as JSON')
|
|
17
|
+
.option('--all', 'Show all reports for this spec')
|
|
18
|
+
.action(async (specId, options) => {
|
|
19
|
+
try {
|
|
20
|
+
specId = validateSpecId(specId);
|
|
21
|
+
const reportsDir = join('.specsafe', 'e2e', 'reports');
|
|
22
|
+
if (!existsSync(reportsDir)) {
|
|
23
|
+
console.log(chalk.yellow('\n⚠️ No reports directory found.'));
|
|
24
|
+
console.log(chalk.gray(' Run tests first: specsafe test-submit ' + specId + ' --screenshots ./screenshots/'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Find reports for this spec
|
|
28
|
+
const files = await readdir(reportsDir);
|
|
29
|
+
const reportFiles = files.filter(f => f.startsWith(specId) && f.endsWith('.json'));
|
|
30
|
+
if (reportFiles.length === 0) {
|
|
31
|
+
console.log(chalk.yellow(`\n⚠️ No reports found for spec: ${specId}`));
|
|
32
|
+
console.log(chalk.gray(' Run tests first: specsafe test-submit ' + specId + ' --screenshots ./screenshots/'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Sort by modification time (latest first)
|
|
36
|
+
const reportsWithStats = await Promise.all(reportFiles.map(async (filename) => {
|
|
37
|
+
const path = join(reportsDir, filename);
|
|
38
|
+
const content = await readFile(path, 'utf-8');
|
|
39
|
+
const report = JSON.parse(content);
|
|
40
|
+
return { filename, report, path };
|
|
41
|
+
}));
|
|
42
|
+
reportsWithStats.sort((a, b) => new Date(b.report.createdAt).getTime() - new Date(a.report.createdAt).getTime());
|
|
43
|
+
if (options.all) {
|
|
44
|
+
// Show list of all reports
|
|
45
|
+
console.log(chalk.blue(`\n📊 Test Reports for ${specId}\n`));
|
|
46
|
+
console.log(`Found ${reportsWithStats.length} report(s):\n`);
|
|
47
|
+
for (let i = 0; i < reportsWithStats.length; i++) {
|
|
48
|
+
const { report, filename } = reportsWithStats[i];
|
|
49
|
+
const date = new Date(report.createdAt).toLocaleString();
|
|
50
|
+
const passRate = (report.summary.passRate * 100).toFixed(1);
|
|
51
|
+
const statusColor = report.summary.passRate === 1 ? chalk.green :
|
|
52
|
+
report.summary.passRate >= 0.8 ? chalk.yellow :
|
|
53
|
+
chalk.red;
|
|
54
|
+
console.log(`${i + 1}. ${filename}`);
|
|
55
|
+
console.log(` Date: ${date}`);
|
|
56
|
+
console.log(` Pass Rate: ${statusColor(passRate + '%')}`);
|
|
57
|
+
console.log(` Steps: ${report.summary.passed}/${report.summary.totalSteps} passed`);
|
|
58
|
+
if (report.summary.failed > 0) {
|
|
59
|
+
console.log(chalk.red(` Failed: ${report.summary.failed}`));
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
}
|
|
63
|
+
console.log(chalk.gray('Use --latest to see the most recent report in detail.'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Show latest report
|
|
67
|
+
const { report } = reportsWithStats[0];
|
|
68
|
+
if (options.json) {
|
|
69
|
+
console.log(JSON.stringify(report, null, 2));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Display formatted report
|
|
73
|
+
displayReport(report);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error(chalk.red('Error:'), error.message);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
/**
|
|
81
|
+
* Display a formatted test report
|
|
82
|
+
*/
|
|
83
|
+
function displayReport(report) {
|
|
84
|
+
const { summary } = report;
|
|
85
|
+
// Header
|
|
86
|
+
console.log(chalk.blue('\n┌─────────────────────────────────────────────────────────────┐'));
|
|
87
|
+
console.log(chalk.blue('│ E2E Test Report │'));
|
|
88
|
+
console.log(chalk.blue('└─────────────────────────────────────────────────────────────┘\n'));
|
|
89
|
+
console.log(chalk.bold(`Spec ID: ${report.specId}`));
|
|
90
|
+
console.log(chalk.gray(`Report ID: ${report.reportId}`));
|
|
91
|
+
console.log(chalk.gray(`Submitted By: ${report.submittedBy}`));
|
|
92
|
+
console.log(chalk.gray(`Generated: ${new Date(report.createdAt).toLocaleString()}`));
|
|
93
|
+
console.log();
|
|
94
|
+
// Summary Box
|
|
95
|
+
const passRatePercent = (summary.passRate * 100).toFixed(1);
|
|
96
|
+
const passRateColor = summary.passRate === 1 ? chalk.green :
|
|
97
|
+
summary.passRate >= 0.8 ? chalk.yellow :
|
|
98
|
+
chalk.red;
|
|
99
|
+
console.log(chalk.bold('📊 Summary\n'));
|
|
100
|
+
console.log(` Total Scenarios: ${summary.totalScenarios}`);
|
|
101
|
+
console.log(` Total Steps: ${summary.totalSteps}`);
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(` ${chalk.green('✅ Passed:')} ${summary.passed.toString().padStart(3)}`);
|
|
104
|
+
console.log(` ${chalk.red('❌ Failed:')} ${summary.failed.toString().padStart(3)}`);
|
|
105
|
+
console.log(` ${chalk.yellow('⚠️ Partial:')} ${summary.partial.toString().padStart(3)}`);
|
|
106
|
+
console.log(` ${chalk.gray('⏭️ Skipped:')} ${summary.skipped.toString().padStart(3)}`);
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(` Pass Rate: ${passRateColor(passRatePercent + '%')}`);
|
|
109
|
+
console.log();
|
|
110
|
+
// Issues
|
|
111
|
+
if (summary.criticalIssues > 0 || summary.highIssues > 0 ||
|
|
112
|
+
summary.mediumIssues > 0 || summary.lowIssues > 0) {
|
|
113
|
+
console.log(chalk.bold('🚨 Issues Found\n'));
|
|
114
|
+
if (summary.criticalIssues > 0) {
|
|
115
|
+
console.log(chalk.red(` 🔴 Critical: ${summary.criticalIssues}`));
|
|
116
|
+
}
|
|
117
|
+
if (summary.highIssues > 0) {
|
|
118
|
+
console.log(chalk.red(` 🟠 High: ${summary.highIssues}`));
|
|
119
|
+
}
|
|
120
|
+
if (summary.mediumIssues > 0) {
|
|
121
|
+
console.log(chalk.yellow(` 🟡 Medium: ${summary.mediumIssues}`));
|
|
122
|
+
}
|
|
123
|
+
if (summary.lowIssues > 0) {
|
|
124
|
+
console.log(chalk.gray(` 🟢 Low: ${summary.lowIssues}`));
|
|
125
|
+
}
|
|
126
|
+
console.log();
|
|
127
|
+
}
|
|
128
|
+
// Recommendations
|
|
129
|
+
if (report.recommendations && report.recommendations.length > 0) {
|
|
130
|
+
console.log(chalk.bold('💡 Recommendations\n'));
|
|
131
|
+
for (const rec of report.recommendations) {
|
|
132
|
+
console.log(` ${rec}`);
|
|
133
|
+
}
|
|
134
|
+
console.log();
|
|
135
|
+
}
|
|
136
|
+
// Detailed Results
|
|
137
|
+
console.log(chalk.bold('📝 Detailed Results\n'));
|
|
138
|
+
// Group results by scenario
|
|
139
|
+
const resultsByScenario = new Map();
|
|
140
|
+
for (const result of report.results) {
|
|
141
|
+
if (!resultsByScenario.has(result.scenarioId)) {
|
|
142
|
+
resultsByScenario.set(result.scenarioId, []);
|
|
143
|
+
}
|
|
144
|
+
resultsByScenario.get(result.scenarioId).push(result);
|
|
145
|
+
}
|
|
146
|
+
for (const [scenarioId, results] of resultsByScenario) {
|
|
147
|
+
console.log(chalk.cyan(` Scenario: ${scenarioId}`));
|
|
148
|
+
for (const result of results) {
|
|
149
|
+
const icon = result.status === 'pass' ? chalk.green('✅') :
|
|
150
|
+
result.status === 'fail' ? chalk.red('❌') :
|
|
151
|
+
result.status === 'partial' ? chalk.yellow('⚠️') : chalk.gray('⏭️');
|
|
152
|
+
console.log(` ${icon} Step ${result.stepId}: ${result.analysis.substring(0, 60)}${result.analysis.length > 60 ? '...' : ''}`);
|
|
153
|
+
if (result.issues.length > 0) {
|
|
154
|
+
for (const issue of result.issues) {
|
|
155
|
+
const severityColor = issue.severity === 'critical' ? chalk.red :
|
|
156
|
+
issue.severity === 'high' ? chalk.red :
|
|
157
|
+
issue.severity === 'medium' ? chalk.yellow : chalk.gray;
|
|
158
|
+
console.log(severityColor(` • ${issue.severity.toUpperCase()}: ${issue.description}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
console.log();
|
|
163
|
+
}
|
|
164
|
+
// Fix Suggestions
|
|
165
|
+
if (report.fixSuggestions && report.fixSuggestions.length > 0) {
|
|
166
|
+
console.log(chalk.bold('🔧 Suggested Fixes\n'));
|
|
167
|
+
for (const fix of report.fixSuggestions.slice(0, 5)) { // Show first 5
|
|
168
|
+
const priorityColor = fix.priority === 'P0' ? chalk.red :
|
|
169
|
+
fix.priority === 'P1' ? chalk.yellow : chalk.gray;
|
|
170
|
+
console.log(` ${priorityColor(`[${fix.priority}]`)} ${fix.scenarioId} - ${fix.stepId}`);
|
|
171
|
+
console.log(` Issue: ${fix.issue}`);
|
|
172
|
+
console.log(` Fix: ${fix.suggestion}`);
|
|
173
|
+
console.log();
|
|
174
|
+
}
|
|
175
|
+
if (report.fixSuggestions.length > 5) {
|
|
176
|
+
console.log(chalk.gray(` ... and ${report.fixSuggestions.length - 5} more suggestions`));
|
|
177
|
+
console.log();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Footer
|
|
181
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
182
|
+
if (summary.passRate === 1) {
|
|
183
|
+
console.log(chalk.green('\n 🎉 All tests passed!'));
|
|
184
|
+
}
|
|
185
|
+
else if (summary.passRate >= 0.8) {
|
|
186
|
+
console.log(chalk.yellow('\n ⚠️ Most tests passed. Review the failures above.'));
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log(chalk.red('\n ❌ Significant issues found. Please fix and re-test.'));
|
|
190
|
+
}
|
|
191
|
+
// Location of full report
|
|
192
|
+
const markdownPath = join('.specsafe', 'e2e', 'reports', `${report.specId}-report.md`);
|
|
193
|
+
console.log(chalk.gray(`\n Full report: ${markdownPath}`));
|
|
194
|
+
console.log();
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=test-report.js.map
|