superspec 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/LICENSE +21 -0
- package/README.md +328 -0
- package/dist/cli/commands/archive.d.ts +7 -0
- package/dist/cli/commands/archive.d.ts.map +1 -0
- package/dist/cli/commands/archive.js +322 -0
- package/dist/cli/commands/archive.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +306 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +9 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +268 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/show.d.ts +6 -0
- package/dist/cli/commands/show.d.ts.map +1 -0
- package/dist/cli/commands/show.js +273 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +8 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +209 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +7 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +328 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +6 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +290 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +87 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/display.d.ts +189 -0
- package/dist/cli/ui/display.d.ts.map +1 -0
- package/dist/cli/ui/display.js +449 -0
- package/dist/cli/ui/display.js.map +1 -0
- package/dist/cli/ui/index.d.ts +12 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +71 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/interactive.d.ts +21 -0
- package/dist/cli/ui/interactive.d.ts.map +1 -0
- package/dist/cli/ui/interactive.js +60 -0
- package/dist/cli/ui/interactive.js.map +1 -0
- package/dist/cli/ui/palette.d.ts +301 -0
- package/dist/cli/ui/palette.d.ts.map +1 -0
- package/dist/cli/ui/palette.js +673 -0
- package/dist/cli/ui/palette.js.map +1 -0
- package/dist/cli/ui/prompts.d.ts +62 -0
- package/dist/cli/ui/prompts.d.ts.map +1 -0
- package/dist/cli/ui/prompts.js +119 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/ui/spinner.d.ts +38 -0
- package/dist/cli/ui/spinner.d.ts.map +1 -0
- package/dist/cli/ui/spinner.js +72 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/core/config/project-config.d.ts +100 -0
- package/dist/core/config/project-config.d.ts.map +1 -0
- package/dist/core/config/project-config.js +87 -0
- package/dist/core/config/project-config.js.map +1 -0
- package/dist/core/parsers/spec-parser.d.ts +48 -0
- package/dist/core/parsers/spec-parser.d.ts.map +1 -0
- package/dist/core/parsers/spec-parser.js +322 -0
- package/dist/core/parsers/spec-parser.js.map +1 -0
- package/dist/core/validation/spec-validator.d.ts +32 -0
- package/dist/core/validation/spec-validator.d.ts.map +1 -0
- package/dist/core/validation/spec-validator.js +242 -0
- package/dist/core/validation/spec-validator.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
- package/schemas/superspec-workflow/schema.yaml +166 -0
- package/templates/design.md +71 -0
- package/templates/plan.md +78 -0
- package/templates/proposal.md +36 -0
- package/templates/spec.md +57 -0
- package/templates/tasks.md +29 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import { PALETTE, SYMBOLS, commandHeader, sectionDivider, createProgressBar, startSpinner, spinnerSuccess, isInteractive, selectChange, } from '../ui/index.js';
|
|
5
|
+
export async function verifyCommand(id, options) {
|
|
6
|
+
const superspecPath = join(process.cwd(), 'superspec');
|
|
7
|
+
if (!existsSync(superspecPath)) {
|
|
8
|
+
console.log(PALETTE.error('SuperSpec not initialized. Run: superspec init'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
// Interactive mode if no ID provided
|
|
12
|
+
if (!id) {
|
|
13
|
+
if (!isInteractive()) {
|
|
14
|
+
console.log(PALETTE.error('Please specify a change ID to verify.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const changesPath = join(superspecPath, 'changes');
|
|
18
|
+
const changes = [];
|
|
19
|
+
if (existsSync(changesPath)) {
|
|
20
|
+
const entries = readdirSync(changesPath, { withFileTypes: true });
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (entry.isDirectory() && entry.name !== 'archive') {
|
|
23
|
+
changes.push(entry.name);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (changes.length === 0) {
|
|
28
|
+
console.log(PALETTE.warning('No active changes found.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
id = await selectChange(changes, 'Select a change to verify:');
|
|
32
|
+
}
|
|
33
|
+
const changePath = join(superspecPath, 'changes', id);
|
|
34
|
+
const specsPath = join(changePath, 'specs');
|
|
35
|
+
if (!existsSync(changePath)) {
|
|
36
|
+
console.log(PALETTE.error(`Change not found: ${id}`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (!existsSync(specsPath)) {
|
|
40
|
+
console.log(PALETTE.error(`No specs found for change: ${id}`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
console.log(commandHeader(`verify ${id}`, 'Implementation verification'));
|
|
44
|
+
// Parse specs
|
|
45
|
+
const specSpinner = startSpinner('Parsing specifications...');
|
|
46
|
+
const requirements = await parseSpecs(specsPath);
|
|
47
|
+
spinnerSuccess(specSpinner, `Found ${requirements.length} requirements`);
|
|
48
|
+
// Find test files
|
|
49
|
+
const testSpinner = startSpinner('Scanning test files...');
|
|
50
|
+
const testFiles = await findTestFiles();
|
|
51
|
+
spinnerSuccess(testSpinner, `Found ${testFiles.length} test files`);
|
|
52
|
+
// Match scenarios to tests
|
|
53
|
+
const matchSpinner = startSpinner('Matching scenarios to tests...');
|
|
54
|
+
const result = await matchScenariosToTests(requirements, testFiles, options);
|
|
55
|
+
spinnerSuccess(matchSpinner, 'Analysis complete');
|
|
56
|
+
console.log();
|
|
57
|
+
// Print report
|
|
58
|
+
printVerifyReport(result, options);
|
|
59
|
+
if (result.verdict === 'fail') {
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function parseSpecs(specsPath) {
|
|
64
|
+
const requirements = [];
|
|
65
|
+
const entries = readdirSync(specsPath, { withFileTypes: true });
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
const specFile = join(specsPath, entry.name, 'spec.md');
|
|
69
|
+
if (existsSync(specFile)) {
|
|
70
|
+
const content = readFileSync(specFile, 'utf-8');
|
|
71
|
+
requirements.push(...parseSpecContent(content));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return requirements;
|
|
76
|
+
}
|
|
77
|
+
function parseSpecContent(content) {
|
|
78
|
+
const requirements = [];
|
|
79
|
+
const lines = content.split('\n');
|
|
80
|
+
let currentReq = null;
|
|
81
|
+
let currentScenario = null;
|
|
82
|
+
for (const line of lines) {
|
|
83
|
+
// Match requirement
|
|
84
|
+
const reqMatch = line.match(/^### Requirement: (.+)$/);
|
|
85
|
+
if (reqMatch?.[1]) {
|
|
86
|
+
if (currentReq) {
|
|
87
|
+
if (currentScenario) {
|
|
88
|
+
currentReq.scenarios.push(currentScenario);
|
|
89
|
+
}
|
|
90
|
+
requirements.push(currentReq);
|
|
91
|
+
}
|
|
92
|
+
currentReq = { name: reqMatch[1], scenarios: [] };
|
|
93
|
+
currentScenario = null;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Match scenario
|
|
97
|
+
const scenarioMatch = line.match(/^#### Scenario: (.+)$/);
|
|
98
|
+
if (scenarioMatch?.[1] && currentReq) {
|
|
99
|
+
if (currentScenario) {
|
|
100
|
+
currentReq.scenarios.push(currentScenario);
|
|
101
|
+
}
|
|
102
|
+
currentScenario = { name: scenarioMatch[1], when: '', then: [] };
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Match WHEN
|
|
106
|
+
const whenMatch = line.match(/^\s*-\s*\*\*WHEN\*\*\s*(.+)$/);
|
|
107
|
+
if (whenMatch?.[1] && currentScenario) {
|
|
108
|
+
currentScenario.when = whenMatch[1];
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
// Match THEN
|
|
112
|
+
const thenMatch = line.match(/^\s*-\s*\*\*THEN\*\*\s*(.+)$/);
|
|
113
|
+
if (thenMatch?.[1] && currentScenario) {
|
|
114
|
+
currentScenario.then.push(thenMatch[1]);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// Match AND
|
|
118
|
+
const andMatch = line.match(/^\s*-\s*\*\*AND\*\*\s*(.+)$/);
|
|
119
|
+
if (andMatch?.[1] && currentScenario) {
|
|
120
|
+
currentScenario.then.push(andMatch[1]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Don't forget the last ones
|
|
124
|
+
if (currentReq) {
|
|
125
|
+
if (currentScenario) {
|
|
126
|
+
currentReq.scenarios.push(currentScenario);
|
|
127
|
+
}
|
|
128
|
+
requirements.push(currentReq);
|
|
129
|
+
}
|
|
130
|
+
return requirements;
|
|
131
|
+
}
|
|
132
|
+
async function findTestFiles() {
|
|
133
|
+
const patterns = [
|
|
134
|
+
'**/*.test.ts',
|
|
135
|
+
'**/*.test.js',
|
|
136
|
+
'**/*.spec.ts',
|
|
137
|
+
'**/*.spec.js',
|
|
138
|
+
'**/test/**/*.ts',
|
|
139
|
+
'**/test/**/*.js',
|
|
140
|
+
'**/tests/**/*.ts',
|
|
141
|
+
'**/tests/**/*.js',
|
|
142
|
+
];
|
|
143
|
+
const files = [];
|
|
144
|
+
for (const pattern of patterns) {
|
|
145
|
+
const matches = await glob(pattern, {
|
|
146
|
+
ignore: ['node_modules/**', 'dist/**'],
|
|
147
|
+
});
|
|
148
|
+
files.push(...matches);
|
|
149
|
+
}
|
|
150
|
+
return [...new Set(files)];
|
|
151
|
+
}
|
|
152
|
+
async function matchScenariosToTests(requirements, testFiles, options) {
|
|
153
|
+
const allTests = [];
|
|
154
|
+
// Parse test files to find test names
|
|
155
|
+
for (const file of testFiles) {
|
|
156
|
+
if (existsSync(file)) {
|
|
157
|
+
const content = readFileSync(file, 'utf-8');
|
|
158
|
+
const lines = content.split('\n');
|
|
159
|
+
for (let i = 0; i < lines.length; i++) {
|
|
160
|
+
const line = lines[i];
|
|
161
|
+
// Match test/it/describe patterns
|
|
162
|
+
const testMatch = line?.match(/(?:test|it|describe)\s*\(\s*['"`]([^'"`]+)['"`]/);
|
|
163
|
+
if (testMatch?.[1]) {
|
|
164
|
+
allTests.push({
|
|
165
|
+
file,
|
|
166
|
+
line: i + 1,
|
|
167
|
+
name: testMatch[1],
|
|
168
|
+
confidence: 'high',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Match scenarios to tests
|
|
175
|
+
const missingTests = [];
|
|
176
|
+
let matchedScenarios = 0;
|
|
177
|
+
for (const req of requirements) {
|
|
178
|
+
for (const scenario of req.scenarios) {
|
|
179
|
+
const match = findMatchingTest(scenario.name, allTests);
|
|
180
|
+
if (match) {
|
|
181
|
+
scenario.testMatch = match;
|
|
182
|
+
matchedScenarios++;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
missingTests.push(scenario);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Find extra tests (not matching any scenario)
|
|
190
|
+
const matchedTestNames = new Set(requirements
|
|
191
|
+
.flatMap(r => r.scenarios)
|
|
192
|
+
.filter(s => s.testMatch)
|
|
193
|
+
.map(s => s.testMatch.name));
|
|
194
|
+
const extraTests = options.strict
|
|
195
|
+
? allTests.filter(t => !matchedTestNames.has(t.name))
|
|
196
|
+
: [];
|
|
197
|
+
const totalScenarios = requirements.reduce((sum, r) => sum + r.scenarios.length, 0);
|
|
198
|
+
const reqsWithCoverage = requirements.filter(r => r.scenarios.every(s => s.testMatch)).length;
|
|
199
|
+
return {
|
|
200
|
+
requirements,
|
|
201
|
+
coverage: {
|
|
202
|
+
requirements: reqsWithCoverage,
|
|
203
|
+
requirementsTotal: requirements.length,
|
|
204
|
+
scenarios: matchedScenarios,
|
|
205
|
+
scenariosTotal: totalScenarios,
|
|
206
|
+
},
|
|
207
|
+
issues: {
|
|
208
|
+
missingTests,
|
|
209
|
+
extraTests,
|
|
210
|
+
},
|
|
211
|
+
verdict: missingTests.length === 0 ? 'pass' : 'fail',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function findMatchingTest(scenarioName, tests) {
|
|
215
|
+
// Normalize names for comparison
|
|
216
|
+
const normalized = scenarioName.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
217
|
+
const words = normalized.split(' ').filter(w => w.length > 2);
|
|
218
|
+
let bestMatch;
|
|
219
|
+
let bestScore = 0;
|
|
220
|
+
for (const test of tests) {
|
|
221
|
+
const testNormalized = test.name.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
|
|
222
|
+
// Exact match
|
|
223
|
+
if (testNormalized === normalized) {
|
|
224
|
+
return { ...test, confidence: 'high' };
|
|
225
|
+
}
|
|
226
|
+
// Word matching score
|
|
227
|
+
const testWords = testNormalized.split(' ').filter(w => w.length > 2);
|
|
228
|
+
const matchingWords = words.filter(w => testWords.includes(w));
|
|
229
|
+
const score = matchingWords.length / Math.max(words.length, testWords.length);
|
|
230
|
+
if (score > bestScore && score >= 0.5) {
|
|
231
|
+
bestScore = score;
|
|
232
|
+
bestMatch = {
|
|
233
|
+
...test,
|
|
234
|
+
confidence: score >= 0.8 ? 'high' : score >= 0.6 ? 'medium' : 'low',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return bestMatch;
|
|
239
|
+
}
|
|
240
|
+
function printVerifyReport(result, options) {
|
|
241
|
+
// Coverage summary
|
|
242
|
+
console.log();
|
|
243
|
+
console.log(sectionDivider());
|
|
244
|
+
console.log();
|
|
245
|
+
console.log(` ${PALETTE.bold(PALETTE.white('📊 Coverage Summary'))}`);
|
|
246
|
+
console.log();
|
|
247
|
+
const reqPercent = result.coverage.requirementsTotal > 0
|
|
248
|
+
? Math.round((result.coverage.requirements / result.coverage.requirementsTotal) * 100)
|
|
249
|
+
: 0;
|
|
250
|
+
const scenarioPercent = result.coverage.scenariosTotal > 0
|
|
251
|
+
? Math.round((result.coverage.scenarios / result.coverage.scenariosTotal) * 100)
|
|
252
|
+
: 0;
|
|
253
|
+
console.log(` ${PALETTE.midGray('Requirements:')} ${createProgressBar(result.coverage.requirements, result.coverage.requirementsTotal, { width: 20 })} ${result.coverage.requirements}/${result.coverage.requirementsTotal} ${PALETTE.midGray(`(${reqPercent}%)`)}`);
|
|
254
|
+
console.log(` ${PALETTE.midGray('Scenarios: ')} ${createProgressBar(result.coverage.scenarios, result.coverage.scenariosTotal, { width: 20 })} ${result.coverage.scenarios}/${result.coverage.scenariosTotal} ${PALETTE.midGray(`(${scenarioPercent}%)`)}`);
|
|
255
|
+
// Requirement coverage detail
|
|
256
|
+
if (options.verbose) {
|
|
257
|
+
console.log();
|
|
258
|
+
console.log(sectionDivider());
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(` ${PALETTE.bold(PALETTE.white('📋 Requirement Coverage'))}`);
|
|
261
|
+
console.log();
|
|
262
|
+
for (const req of result.requirements) {
|
|
263
|
+
const allCovered = req.scenarios.every(s => s.testMatch);
|
|
264
|
+
const icon = allCovered
|
|
265
|
+
? PALETTE.success(SYMBOLS.success)
|
|
266
|
+
: PALETTE.error(SYMBOLS.error);
|
|
267
|
+
console.log(` ${icon} ${PALETTE.primary(req.name)}`);
|
|
268
|
+
for (const scenario of req.scenarios) {
|
|
269
|
+
const sIcon = scenario.testMatch
|
|
270
|
+
? PALETTE.success(' ' + SYMBOLS.success)
|
|
271
|
+
: PALETTE.error(' ' + SYMBOLS.error);
|
|
272
|
+
if (scenario.testMatch) {
|
|
273
|
+
const confidence = scenario.testMatch.confidence === 'high'
|
|
274
|
+
? ''
|
|
275
|
+
: PALETTE.warning(` [${scenario.testMatch.confidence}]`);
|
|
276
|
+
console.log(` ${sIcon} ${scenario.name}${confidence}`);
|
|
277
|
+
console.log(` ${PALETTE.darkGray('→')} ${PALETTE.midGray(`${scenario.testMatch.file}:${scenario.testMatch.line}`)}`);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
console.log(` ${sIcon} ${scenario.name} ${PALETTE.error('(no test found)')}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
console.log();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Issues
|
|
287
|
+
if (result.issues.missingTests.length > 0) {
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(sectionDivider());
|
|
290
|
+
console.log();
|
|
291
|
+
console.log(` ${PALETTE.bold(PALETTE.white('⚠️ Missing Tests'))}`);
|
|
292
|
+
console.log();
|
|
293
|
+
for (const scenario of result.issues.missingTests) {
|
|
294
|
+
console.log(` ${PALETTE.error(SYMBOLS.error)} ${scenario.name}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (result.issues.extraTests.length > 0) {
|
|
298
|
+
console.log();
|
|
299
|
+
console.log(sectionDivider());
|
|
300
|
+
console.log();
|
|
301
|
+
console.log(` ${PALETTE.bold(PALETTE.white('📝 Extra Tests (not in Specs)'))}`);
|
|
302
|
+
console.log();
|
|
303
|
+
const displayCount = Math.min(result.issues.extraTests.length, 10);
|
|
304
|
+
for (const test of result.issues.extraTests.slice(0, displayCount)) {
|
|
305
|
+
console.log(` ${PALETTE.warning(SYMBOLS.warning)} ${test.name}`);
|
|
306
|
+
console.log(` ${PALETTE.midGray(`${test.file}:${test.line}`)}`);
|
|
307
|
+
}
|
|
308
|
+
if (result.issues.extraTests.length > 10) {
|
|
309
|
+
console.log(` ${PALETTE.midGray(`... and ${result.issues.extraTests.length - 10} more`)}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Verdict
|
|
313
|
+
console.log();
|
|
314
|
+
console.log(sectionDivider());
|
|
315
|
+
console.log();
|
|
316
|
+
if (result.verdict === 'pass') {
|
|
317
|
+
console.log(` ${PALETTE.success(SYMBOLS.success)} ${PALETTE.bold(PALETTE.success('READY TO ARCHIVE'))}`);
|
|
318
|
+
console.log();
|
|
319
|
+
console.log(` ${PALETTE.midGray('Next:')} ${PALETTE.white('superspec archive <change-id>')}`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
console.log(` ${PALETTE.error(SYMBOLS.error)} ${PALETTE.bold(PALETTE.error('NEEDS WORK'))}`);
|
|
323
|
+
console.log();
|
|
324
|
+
console.log(` ${PALETTE.midGray('Write tests for missing scenarios before archiving.')}`);
|
|
325
|
+
}
|
|
326
|
+
console.log();
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/cli/commands/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACL,OAAO,EACP,OAAO,EACP,aAAa,EACb,cAAc,EAEd,iBAAiB,EACjB,YAAY,EACZ,cAAc,EAEd,aAAa,EACb,YAAY,GACb,MAAM,gBAAgB,CAAC;AAyCxB,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAsB,EAAE,OAAsB;IAChF,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;IAEvD,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC,CAAC;QAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,qCAAqC;IACrC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAC;YACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACnD,MAAM,OAAO,GAAa,EAAE,CAAC;QAE7B,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAClE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;oBACpD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC;YACzD,OAAO;QACT,CAAC;QAED,EAAE,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,4BAA4B,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAE5C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE,6BAA6B,CAAC,CAAC,CAAC;IAE1E,cAAc;IACd,MAAM,WAAW,GAAG,YAAY,CAAC,2BAA2B,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC;IACjD,cAAc,CAAC,WAAW,EAAE,SAAS,YAAY,CAAC,MAAM,eAAe,CAAC,CAAC;IAEzE,kBAAkB;IAClB,MAAM,WAAW,GAAG,YAAY,CAAC,wBAAwB,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,cAAc,CAAC,WAAW,EAAE,SAAS,SAAS,CAAC,MAAM,aAAa,CAAC,CAAC;IAEpE,2BAA2B;IAC3B,MAAM,YAAY,GAAG,YAAY,CAAC,gCAAgC,CAAC,CAAC;IACpE,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7E,cAAc,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC;IAElD,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,eAAe;IACf,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,SAAiB;IACzC,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACxD,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACzB,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAChD,YAAY,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe;IACvC,MAAM,YAAY,GAAkB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,IAAI,UAAU,GAAuB,IAAI,CAAC;IAC1C,IAAI,eAAe,GAAoB,IAAI,CAAC;IAE5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,oBAAoB;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACvD,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,IAAI,UAAU,EAAE,CAAC;gBACf,IAAI,eAAe,EAAE,CAAC;oBACpB,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;gBAC7C,CAAC;gBACD,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAChC,CAAC;YACD,UAAU,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;YAClD,eAAe,GAAG,IAAI,CAAC;YACvB,SAAS;QACX,CAAC;QAED,iBAAiB;QACjB,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAC1D,IAAI,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC;YACrC,IAAI,eAAe,EAAE,CAAC;gBACpB,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YAC7C,CAAC;YACD,eAAe,GAAG,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;YACjE,SAAS;QACX,CAAC;QAED,aAAa;QACb,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC7D,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;YACtC,eAAe,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YACpC,SAAS;QACX,CAAC;QAED,aAAa;QACb,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC7D,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;YACtC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YACxC,SAAS;QACX,CAAC;QAED,YAAY;QACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAC3D,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC;YACrC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,eAAe,EAAE,CAAC;YACpB,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7C,CAAC;QACD,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,KAAK,UAAU,aAAa;IAC1B,MAAM,QAAQ,GAAG;QACf,cAAc;QACd,cAAc;QACd,cAAc;QACd,cAAc;QACd,iBAAiB;QACjB,iBAAiB;QACjB,kBAAkB;QAClB,kBAAkB;KACnB,CAAC;IAEF,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE;YAClC,MAAM,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC;SACvC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,YAA2B,EAC3B,SAAmB,EACnB,OAAsB;IAEtB,MAAM,QAAQ,GAAgB,EAAE,CAAC;IAEjC,sCAAsC;IACtC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,kCAAkC;gBAClC,MAAM,SAAS,GAAG,IAAI,EAAE,KAAK,CAAC,iDAAiD,CAAC,CAAC;gBACjF,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACnB,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI;wBACJ,IAAI,EAAE,CAAC,GAAG,CAAC;wBACX,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;wBAClB,UAAU,EAAE,MAAM;qBACnB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,2BAA2B;IAC3B,MAAM,YAAY,GAAe,EAAE,CAAC;IACpC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IAEzB,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACxD,IAAI,KAAK,EAAE,CAAC;gBACV,QAAQ,CAAC,SAAS,GAAG,KAAK,CAAC;gBAC3B,gBAAgB,EAAE,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACN,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IAED,+CAA+C;IAC/C,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAC9B,YAAY;SACT,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;SACxB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAU,CAAC,IAAI,CAAC,CAC/B,CAAC;IAEF,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM;QAC/B,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrD,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,cAAc,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACpF,MAAM,gBAAgB,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAC/C,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CACpC,CAAC,MAAM,CAAC;IAET,OAAO;QACL,YAAY;QACZ,QAAQ,EAAE;YACR,YAAY,EAAE,gBAAgB;YAC9B,iBAAiB,EAAE,YAAY,CAAC,MAAM;YACtC,SAAS,EAAE,gBAAgB;YAC3B,cAAc,EAAE,cAAc;SAC/B;QACD,MAAM,EAAE;YACN,YAAY;YACZ,UAAU;SACX;QACD,OAAO,EAAE,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;KACrD,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,YAAoB,EAAE,KAAkB;IAChE,iCAAiC;IACjC,MAAM,UAAU,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACjF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE9D,IAAI,SAAgC,CAAC;IACrC,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAElF,cAAc;QACd,IAAI,cAAc,KAAK,UAAU,EAAE,CAAC;YAClC,OAAO,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QACzC,CAAC;QAED,sBAAsB;QACtB,MAAM,SAAS,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACtE,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAE9E,IAAI,KAAK,GAAG,SAAS,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;YACtC,SAAS,GAAG,KAAK,CAAC;YAClB,SAAS,GAAG;gBACV,GAAG,IAAI;gBACP,UAAU,EAAE,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;aACpE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAoB,EAAE,OAAsB;IACrE,mBAAmB;IACnB,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,iBAAiB,GAAG,CAAC;QACtD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAC,GAAG,GAAG,CAAC;QACtF,CAAC,CAAC,CAAC,CAAC;IACN,MAAM,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC,cAAc,GAAG,CAAC;QACxD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,GAAG,CAAC;QAChF,CAAC,CAAC,CAAC,CAAC;IAEN,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,iBAAiB,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,IAAI,MAAM,CAAC,QAAQ,CAAC,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC,CAAC;IACtQ,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC,CAAC;IAE/P,8BAA8B;IAC9B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,GAAG,EAAE,CAAC;QAEd,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YACtC,MAAM,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACzD,MAAM,IAAI,GAAG,UAAU;gBACrB,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;gBAClC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEtD,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS;oBAC9B,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;oBACzC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;gBAExC,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;oBACvB,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,CAAC,UAAU,KAAK,MAAM;wBACzD,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,SAAS,CAAC,UAAU,GAAG,CAAC,CAAC;oBAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,QAAQ,CAAC,IAAI,GAAG,UAAU,EAAE,CAAC,CAAC;oBACxD,OAAO,CAAC,GAAG,CAAC,SAAS,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC5H,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,IAAI,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;gBACjF,CAAC;YACH,CAAC;YACD,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,SAAS;IACT,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC,EAAE,CAAC,CAAC;QACjF,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAClE,OAAO,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAED,UAAU;IACV,OAAO,CAAC,GAAG,EAAE,CAAC;IACd,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;IAC9B,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1G,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,EAAE,CAAC,CAAC;IACjG,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9F,OAAO,CAAC,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,qDAAqD,CAAC,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,OAAO,CAAC,GAAG,EAAE,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/view.ts"],"names":[],"mappings":"AAiBA,UAAU,WAAW;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA8BD,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA8KrE"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import { PALETTE, ICONS, BOX, sectionHeader, createProgressBar, startSpinner, spinnerSuccess, isInteractive, selectPrompt, displayCommand, displayStats, } from '../ui/index.js';
|
|
5
|
+
export async function viewCommand(options) {
|
|
6
|
+
const superspecPath = join(process.cwd(), 'superspec');
|
|
7
|
+
if (!existsSync(superspecPath)) {
|
|
8
|
+
console.log(` ${ICONS.error} ${PALETTE.error('SuperSpec not initialized.')}`);
|
|
9
|
+
console.log(` ${PALETTE.dim('Run:')} ${PALETTE.accent('superspec init')}`);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const spinner = startSpinner('Loading project overview...');
|
|
13
|
+
const overview = gatherProjectOverview(superspecPath);
|
|
14
|
+
spinnerSuccess(spinner, 'Project loaded');
|
|
15
|
+
if (options.json) {
|
|
16
|
+
console.log(JSON.stringify(overview, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
// DASHBOARD HEADER
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
console.log();
|
|
23
|
+
console.log(` ${PALETTE.subtle(BOX.heavyTopLeft + BOX.heavyH.repeat(58) + BOX.heavyTopRight)}`);
|
|
24
|
+
console.log(` ${PALETTE.subtle(BOX.heavyV)} ${PALETTE.primary('⬡')} ${PALETTE.bold(PALETTE.white('SuperSpec Dashboard'))}${' '.repeat(36)}${PALETTE.subtle(BOX.heavyV)}`);
|
|
25
|
+
console.log(` ${PALETTE.subtle(BOX.heavyBottomLeft + BOX.heavyH.repeat(58) + BOX.heavyBottomRight)}`);
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
// PROJECT STATISTICS
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
console.log(sectionHeader('Project Statistics', '📊'));
|
|
30
|
+
console.log();
|
|
31
|
+
displayStats([
|
|
32
|
+
{ label: 'Main Specs', value: overview.stats.mainSpecCount },
|
|
33
|
+
{ label: 'Active', value: overview.stats.activeChangeCount },
|
|
34
|
+
{ label: 'Archived', value: overview.stats.archivedChangeCount },
|
|
35
|
+
]);
|
|
36
|
+
console.log();
|
|
37
|
+
displayStats([
|
|
38
|
+
{ label: 'Requirements', value: overview.stats.totalRequirements, icon: PALETTE.primary('◆') },
|
|
39
|
+
{ label: 'Scenarios', value: overview.stats.totalScenarios, icon: PALETTE.accent('◆') },
|
|
40
|
+
]);
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
+
// ACTIVE CHANGES
|
|
43
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
44
|
+
console.log(sectionHeader('Active Changes', '🔄'));
|
|
45
|
+
console.log();
|
|
46
|
+
if (overview.activeChanges.length > 0) {
|
|
47
|
+
for (const change of overview.activeChanges) {
|
|
48
|
+
const phaseIcon = getPhaseIcon(change.phase);
|
|
49
|
+
const title = change.title
|
|
50
|
+
? PALETTE.dim(` - ${change.title.slice(0, 35)}${change.title.length > 35 ? '…' : ''}`)
|
|
51
|
+
: '';
|
|
52
|
+
console.log(` ${PALETTE.subtle('┌─')} ${phaseIcon} ${PALETTE.bold(PALETTE.white(change.id))}${title}`);
|
|
53
|
+
// Progress bar if in execute phase
|
|
54
|
+
if (change.progress && change.progress.total > 0) {
|
|
55
|
+
const { completed, total } = change.progress;
|
|
56
|
+
const bar = createProgressBar(completed, total, { width: 18, showCount: true });
|
|
57
|
+
console.log(` ${PALETTE.subtle('│')} ${bar}`);
|
|
58
|
+
}
|
|
59
|
+
// Phase indicator
|
|
60
|
+
const phases = ['proposal', 'design', 'spec', 'plan', 'execute', 'verify'];
|
|
61
|
+
const currentPhaseIndex = phases.indexOf(change.phase);
|
|
62
|
+
const phaseBar = phases.map((p, i) => {
|
|
63
|
+
if (i < currentPhaseIndex)
|
|
64
|
+
return PALETTE.success('●');
|
|
65
|
+
if (i === currentPhaseIndex)
|
|
66
|
+
return PALETTE.accent('●');
|
|
67
|
+
return PALETTE.dark('○');
|
|
68
|
+
}).join(' ');
|
|
69
|
+
console.log(` ${PALETTE.subtle('│')} ${PALETTE.dim('Phase:')} ${phaseBar}`);
|
|
70
|
+
console.log(` ${PALETTE.subtle('└─')}`);
|
|
71
|
+
console.log();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(` ${PALETTE.dim('No active changes.')}`);
|
|
76
|
+
console.log();
|
|
77
|
+
displayCommand('/superspec:brainstorm', 'Start a new change');
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
81
|
+
// MAIN SPECIFICATIONS
|
|
82
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
83
|
+
if (overview.mainSpecs.length > 0) {
|
|
84
|
+
console.log(sectionHeader('Main Specifications', '📋'));
|
|
85
|
+
console.log();
|
|
86
|
+
// Table header
|
|
87
|
+
console.log(` ${PALETTE.bold(PALETTE.dim('Name'.padEnd(28)))} ${PALETTE.bold(PALETTE.dim('Reqs'.padStart(8)))} ${PALETTE.bold(PALETTE.dim('Scenarios'.padStart(10)))}`);
|
|
88
|
+
console.log(` ${PALETTE.dark('─'.repeat(28))} ${PALETTE.dark('─'.repeat(8))} ${PALETTE.dark('─'.repeat(10))}`);
|
|
89
|
+
for (const spec of overview.mainSpecs.slice(0, 8)) {
|
|
90
|
+
const name = spec.name.length > 27
|
|
91
|
+
? spec.name.slice(0, 25) + '…'
|
|
92
|
+
: spec.name;
|
|
93
|
+
console.log(` ${PALETTE.primaryBright(name.padEnd(28))} ${PALETTE.white(String(spec.requirementCount).padStart(8))} ${PALETTE.white(String(spec.scenarioCount).padStart(10))}`);
|
|
94
|
+
}
|
|
95
|
+
if (overview.mainSpecs.length > 8) {
|
|
96
|
+
console.log(` ${PALETTE.dim(`... and ${overview.mainSpecs.length - 8} more`)}`);
|
|
97
|
+
}
|
|
98
|
+
console.log();
|
|
99
|
+
}
|
|
100
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
101
|
+
// QUICK ACTIONS
|
|
102
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
103
|
+
console.log(sectionHeader('Quick Actions', '🚀'));
|
|
104
|
+
console.log();
|
|
105
|
+
displayCommand('superspec list', 'View all changes');
|
|
106
|
+
displayCommand('superspec list -s', 'View all specs');
|
|
107
|
+
displayCommand('superspec validate', 'Validate specs');
|
|
108
|
+
displayCommand('/superspec:brainstorm', 'Start new change');
|
|
109
|
+
console.log();
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
111
|
+
// INTERACTIVE NAVIGATION
|
|
112
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
113
|
+
if (isInteractive() && (overview.activeChanges.length > 0 || overview.mainSpecs.length > 0)) {
|
|
114
|
+
console.log(` ${PALETTE.subtle('─'.repeat(55))}`);
|
|
115
|
+
console.log();
|
|
116
|
+
const choices = [
|
|
117
|
+
{ name: `${PALETTE.primary('›')} View a change`, value: 'change' },
|
|
118
|
+
{ name: `${PALETTE.primary('›')} View a spec`, value: 'spec' },
|
|
119
|
+
{ name: `${PALETTE.primary('›')} Run validation`, value: 'validate' },
|
|
120
|
+
{ name: PALETTE.dim(' (Exit)'), value: 'exit' },
|
|
121
|
+
];
|
|
122
|
+
const action = await selectPrompt({
|
|
123
|
+
message: 'What would you like to do?',
|
|
124
|
+
choices,
|
|
125
|
+
});
|
|
126
|
+
if (action === 'change' && overview.activeChanges.length > 0) {
|
|
127
|
+
const { showCommand } = await import('./show.js');
|
|
128
|
+
const changeChoices = overview.activeChanges.map(c => ({
|
|
129
|
+
name: `${c.id}${c.title ? ` - ${c.title}` : ''}`,
|
|
130
|
+
value: c.id,
|
|
131
|
+
}));
|
|
132
|
+
const selectedChange = await selectPrompt({
|
|
133
|
+
message: 'Select a change:',
|
|
134
|
+
choices: [...changeChoices, { name: PALETTE.dim('(Cancel)'), value: '' }],
|
|
135
|
+
});
|
|
136
|
+
if (selectedChange) {
|
|
137
|
+
await showCommand(selectedChange, {});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (action === 'spec' && overview.mainSpecs.length > 0) {
|
|
141
|
+
const { showCommand } = await import('./show.js');
|
|
142
|
+
const specChoices = overview.mainSpecs.map(s => ({
|
|
143
|
+
name: `${s.name} (${s.requirementCount} requirements)`,
|
|
144
|
+
value: s.name,
|
|
145
|
+
}));
|
|
146
|
+
const selectedSpec = await selectPrompt({
|
|
147
|
+
message: 'Select a spec:',
|
|
148
|
+
choices: [...specChoices, { name: PALETTE.dim('(Cancel)'), value: '' }],
|
|
149
|
+
});
|
|
150
|
+
if (selectedSpec) {
|
|
151
|
+
await showCommand(selectedSpec, {});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (action === 'validate') {
|
|
155
|
+
const { validateCommand } = await import('./validate.js');
|
|
156
|
+
await validateCommand(undefined, { all: true });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function gatherProjectOverview(superspecPath) {
|
|
161
|
+
const overview = {
|
|
162
|
+
initialized: true,
|
|
163
|
+
activeChanges: [],
|
|
164
|
+
archivedChanges: 0,
|
|
165
|
+
mainSpecs: [],
|
|
166
|
+
stats: {
|
|
167
|
+
totalRequirements: 0,
|
|
168
|
+
totalScenarios: 0,
|
|
169
|
+
activeChangeCount: 0,
|
|
170
|
+
archivedChangeCount: 0,
|
|
171
|
+
mainSpecCount: 0,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
// Gather active changes
|
|
175
|
+
const changesPath = join(superspecPath, 'changes');
|
|
176
|
+
if (existsSync(changesPath)) {
|
|
177
|
+
const entries = readdirSync(changesPath, { withFileTypes: true });
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
if (entry.isDirectory() && entry.name !== 'archive') {
|
|
180
|
+
const changePath = join(changesPath, entry.name);
|
|
181
|
+
overview.activeChanges.push(getChangeOverview(entry.name, changePath));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Count archived changes
|
|
186
|
+
const archivePath = join(changesPath, 'archive');
|
|
187
|
+
if (existsSync(archivePath)) {
|
|
188
|
+
const entries = readdirSync(archivePath, { withFileTypes: true });
|
|
189
|
+
overview.archivedChanges = entries.filter(e => e.isDirectory()).length;
|
|
190
|
+
}
|
|
191
|
+
// Gather main specs
|
|
192
|
+
const specsPath = join(superspecPath, 'specs');
|
|
193
|
+
if (existsSync(specsPath)) {
|
|
194
|
+
const entries = readdirSync(specsPath, { withFileTypes: true });
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (entry.isDirectory()) {
|
|
197
|
+
const specFile = join(specsPath, entry.name, 'spec.md');
|
|
198
|
+
if (existsSync(specFile)) {
|
|
199
|
+
overview.mainSpecs.push(getSpecOverview(entry.name, specFile));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Calculate stats
|
|
205
|
+
overview.stats.activeChangeCount = overview.activeChanges.length;
|
|
206
|
+
overview.stats.archivedChangeCount = overview.archivedChanges;
|
|
207
|
+
overview.stats.mainSpecCount = overview.mainSpecs.length;
|
|
208
|
+
overview.stats.totalRequirements = overview.mainSpecs.reduce((sum, s) => sum + s.requirementCount, 0);
|
|
209
|
+
overview.stats.totalScenarios = overview.mainSpecs.reduce((sum, s) => sum + s.scenarioCount, 0);
|
|
210
|
+
return overview;
|
|
211
|
+
}
|
|
212
|
+
function getChangeOverview(id, path) {
|
|
213
|
+
const overview = {
|
|
214
|
+
id,
|
|
215
|
+
phase: 'proposal',
|
|
216
|
+
specCount: 0,
|
|
217
|
+
};
|
|
218
|
+
// Get title from proposal
|
|
219
|
+
const proposalPath = join(path, 'proposal.md');
|
|
220
|
+
if (existsSync(proposalPath)) {
|
|
221
|
+
try {
|
|
222
|
+
const content = readFileSync(proposalPath, 'utf-8');
|
|
223
|
+
const { data } = matter(content);
|
|
224
|
+
overview.title = data['title'];
|
|
225
|
+
}
|
|
226
|
+
catch { /* ignore */ }
|
|
227
|
+
}
|
|
228
|
+
// Determine phase
|
|
229
|
+
const hasDesign = existsSync(join(path, 'design.md'));
|
|
230
|
+
const hasSpecs = existsSync(join(path, 'specs'));
|
|
231
|
+
const hasPlan = existsSync(join(path, 'plan.md'));
|
|
232
|
+
const hasTasks = existsSync(join(path, 'tasks.md'));
|
|
233
|
+
if (hasTasks) {
|
|
234
|
+
const content = readFileSync(join(path, 'tasks.md'), 'utf-8');
|
|
235
|
+
const total = (content.match(/^- \[[ x]\]/gm) ?? []).length;
|
|
236
|
+
const completed = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
237
|
+
overview.progress = { completed, total };
|
|
238
|
+
if (completed === total && total > 0) {
|
|
239
|
+
overview.phase = 'verify';
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
overview.phase = 'execute';
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (hasPlan) {
|
|
246
|
+
overview.phase = 'plan';
|
|
247
|
+
}
|
|
248
|
+
else if (hasSpecs) {
|
|
249
|
+
overview.phase = 'spec';
|
|
250
|
+
}
|
|
251
|
+
else if (hasDesign) {
|
|
252
|
+
overview.phase = 'design';
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
overview.phase = 'proposal';
|
|
256
|
+
}
|
|
257
|
+
// Count specs
|
|
258
|
+
const specsPath = join(path, 'specs');
|
|
259
|
+
if (existsSync(specsPath)) {
|
|
260
|
+
const entries = readdirSync(specsPath, { withFileTypes: true });
|
|
261
|
+
overview.specCount = entries.filter(e => e.isDirectory()).length;
|
|
262
|
+
}
|
|
263
|
+
return overview;
|
|
264
|
+
}
|
|
265
|
+
function getSpecOverview(name, path) {
|
|
266
|
+
const overview = {
|
|
267
|
+
name,
|
|
268
|
+
requirementCount: 0,
|
|
269
|
+
scenarioCount: 0,
|
|
270
|
+
};
|
|
271
|
+
try {
|
|
272
|
+
const content = readFileSync(path, 'utf-8');
|
|
273
|
+
overview.requirementCount = (content.match(/^### Requirement:/gm) ?? []).length;
|
|
274
|
+
overview.scenarioCount = (content.match(/^#### Scenario:/gm) ?? []).length;
|
|
275
|
+
}
|
|
276
|
+
catch { /* ignore */ }
|
|
277
|
+
return overview;
|
|
278
|
+
}
|
|
279
|
+
function getPhaseIcon(phase) {
|
|
280
|
+
const icons = {
|
|
281
|
+
proposal: PALETTE.dark('◐'),
|
|
282
|
+
design: PALETTE.dim('◑'),
|
|
283
|
+
spec: PALETTE.primary('◐'),
|
|
284
|
+
plan: PALETTE.primaryBright('◑'),
|
|
285
|
+
execute: PALETTE.accent('◉'),
|
|
286
|
+
verify: PALETTE.success('●'),
|
|
287
|
+
};
|
|
288
|
+
return icons[phase] ?? PALETTE.dark('○');
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=view.js.map
|