@aisdlc/core 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +516 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -1
- package/dist/index.d.ts +72 -1
- package/dist/index.js +513 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -294,19 +294,26 @@ function extractPythonFunctions(content, filePath) {
|
|
|
294
294
|
}
|
|
295
295
|
return buildMetricsFromBlocks(blocks, content, filePath);
|
|
296
296
|
}
|
|
297
|
-
function
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
297
|
+
function tryStartBraceFunction(line, state, ext) {
|
|
298
|
+
if (state.currentFn || state.inBlockComment || state.rawTerm) return;
|
|
299
|
+
const candidate = matchFunctionHeader(line, ext);
|
|
300
|
+
if (candidate) {
|
|
301
301
|
state.currentFn = candidate;
|
|
302
302
|
state.braceCount = 0;
|
|
303
303
|
state.fnLines = [];
|
|
304
304
|
}
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
}
|
|
306
|
+
function updateBraceScanState(line, state, ext) {
|
|
307
|
+
const res = scanBracesInLine(line, state.inBlockComment, state.rawTerm, ext);
|
|
307
308
|
state.inBlockComment = res.nowInBlock;
|
|
309
|
+
state.rawTerm = res.nowRawTerm;
|
|
308
310
|
state.braceCount += res.delta;
|
|
309
|
-
|
|
311
|
+
}
|
|
312
|
+
function processBraceLine(line, state, blocks, lineIndex, ext) {
|
|
313
|
+
tryStartBraceFunction(line, state, ext);
|
|
314
|
+
if (state.currentFn) state.fnLines.push(line);
|
|
315
|
+
updateBraceScanState(line, state, ext);
|
|
316
|
+
if (state.currentFn && state.braceCount === 0 && state.fnLines.length > 1) {
|
|
310
317
|
blocks.push({ name: state.currentFn, body: state.fnLines.join("\n"), startLine: lineIndex });
|
|
311
318
|
state.currentFn = null;
|
|
312
319
|
state.fnLines = [];
|
|
@@ -316,98 +323,153 @@ function extractBraceFunctions(content, filePath) {
|
|
|
316
323
|
const ext = path4__namespace.extname(filePath).toLowerCase();
|
|
317
324
|
const lines = content.split("\n");
|
|
318
325
|
const blocks = [];
|
|
319
|
-
const state = {
|
|
326
|
+
const state = {
|
|
327
|
+
currentFn: null,
|
|
328
|
+
braceCount: 0,
|
|
329
|
+
fnLines: [],
|
|
330
|
+
inBlockComment: false,
|
|
331
|
+
rawTerm: null
|
|
332
|
+
};
|
|
320
333
|
for (let i = 0; i < lines.length; i++) {
|
|
321
334
|
processBraceLine(lines[i], state, blocks, i, ext);
|
|
322
335
|
}
|
|
323
336
|
return buildMetricsFromBlocks(blocks, content, filePath);
|
|
324
337
|
}
|
|
338
|
+
var HEADER_EXCLUSIONS = /* @__PURE__ */ new Set([
|
|
339
|
+
"if",
|
|
340
|
+
"for",
|
|
341
|
+
"foreach",
|
|
342
|
+
"while",
|
|
343
|
+
"switch",
|
|
344
|
+
"catch",
|
|
345
|
+
"select",
|
|
346
|
+
"match",
|
|
347
|
+
"class",
|
|
348
|
+
"struct",
|
|
349
|
+
"interface",
|
|
350
|
+
"record",
|
|
351
|
+
"enum",
|
|
352
|
+
"namespace",
|
|
353
|
+
"using",
|
|
354
|
+
"lock",
|
|
355
|
+
"fixed",
|
|
356
|
+
"synchronized",
|
|
357
|
+
"try",
|
|
358
|
+
"else"
|
|
359
|
+
]);
|
|
325
360
|
function matchCStyleHeader(line) {
|
|
326
361
|
const match = line.match(
|
|
327
|
-
/(?:(?:public|private|protected|static|async|virtual|override|fn)\s+)*(?:function\s+([a-zA-Z0-9_$]+)|(?:[a-zA-Z0-9_<>[\]
|
|
362
|
+
/(?:(?:public|private|protected|internal|static|async|virtual|override|sealed|abstract|final|synchronized|default|fn)\s+)*(?:function\s+([a-zA-Z0-9_$]+)|(?:[a-zA-Z0-9_<>[\]?,.\s]+\s+)+([a-zA-Z0-9_$]+)\s*(?:<[^>]+>)?\s*\([^;{}]*\)\s*(?:where\s+[^{]+|throws\s+[^{]+)?\{|([a-zA-Z0-9_$]+)\s*\([^;{}]*\)\s*(?::\s*[^{]+\s*)?\{)/
|
|
328
363
|
);
|
|
329
364
|
if (!match) return null;
|
|
330
365
|
const candidate = match[1] || match[2] || match[3];
|
|
331
|
-
|
|
332
|
-
return candidate && !exclusions.includes(candidate) ? candidate : null;
|
|
366
|
+
return candidate && !HEADER_EXCLUSIONS.has(candidate) ? candidate : null;
|
|
333
367
|
}
|
|
334
368
|
function matchFunctionHeader(line, ext) {
|
|
335
369
|
const trimmed = line.trim();
|
|
336
|
-
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
|
|
370
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("#")) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
337
373
|
if (ext === ".go") {
|
|
338
|
-
const match = line.match(/func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_$]+)\s*\(/);
|
|
374
|
+
const match = line.match(/func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_$]+)\s*(?:\[[^\]]*\])?\s*\(/);
|
|
339
375
|
return match ? match[1] : null;
|
|
340
376
|
}
|
|
341
377
|
if (ext === ".rs") {
|
|
342
|
-
const match = line.match(/(?:pub
|
|
378
|
+
const match = line.match(/(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?(?:const\s+)?fn\s+([a-zA-Z0-9_$]+)\s*(?:<[^>]*>)?\s*\(/);
|
|
343
379
|
return match ? match[1] : null;
|
|
344
380
|
}
|
|
345
381
|
return matchCStyleHeader(line);
|
|
346
382
|
}
|
|
347
|
-
function
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
+
function detectRustOrGoRawString(sub, ext) {
|
|
384
|
+
if (ext === ".rs") {
|
|
385
|
+
const m = sub.match(/^r(#*)"/);
|
|
386
|
+
return m ? { term: `"${m[1]}`, advance: m[0].length - 1 } : null;
|
|
387
|
+
}
|
|
388
|
+
if (ext === ".go" && sub.startsWith("`")) {
|
|
389
|
+
return { term: "`", advance: 0 };
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
function detectCsJavaOrCppRawString(sub, ext) {
|
|
394
|
+
if (ext === ".cs" || ext === ".java") {
|
|
395
|
+
const m = sub.match(/^\$*("{3,})/);
|
|
396
|
+
if (m) return { term: m[1], advance: m[0].length - 1 };
|
|
397
|
+
const v = ext === ".cs" ? sub.match(/^(@\$|\$@|@)"/) : null;
|
|
398
|
+
return v ? { term: '"', advance: v[0].length - 1 } : null;
|
|
399
|
+
}
|
|
400
|
+
const cpp = sub.match(/^R"([a-zA-Z0-9_]*)\(/);
|
|
401
|
+
return cpp ? { term: `)${cpp[1]}"`, advance: cpp[0].length - 1 } : null;
|
|
402
|
+
}
|
|
403
|
+
function detectRawStringOpen(line, index, ext) {
|
|
404
|
+
const sub = line.slice(index);
|
|
405
|
+
return detectRustOrGoRawString(sub, ext) || detectCsJavaOrCppRawString(sub, ext);
|
|
406
|
+
}
|
|
407
|
+
function consumeRawOrBlock(line, index, state) {
|
|
408
|
+
if (state.inBlock) {
|
|
409
|
+
if (line[index] === "*" && line[index + 1] === "/") {
|
|
410
|
+
state.inBlock = false;
|
|
411
|
+
return { advance: 1, handled: true };
|
|
412
|
+
}
|
|
413
|
+
return { advance: 0, handled: true };
|
|
414
|
+
}
|
|
415
|
+
if (state.rawTerm) {
|
|
416
|
+
if (line.startsWith(state.rawTerm, index)) {
|
|
417
|
+
const len = state.rawTerm.length - 1;
|
|
418
|
+
state.rawTerm = null;
|
|
419
|
+
return { advance: len, handled: true };
|
|
420
|
+
}
|
|
421
|
+
return { advance: 0, handled: true };
|
|
422
|
+
}
|
|
423
|
+
return { advance: 0, handled: false };
|
|
424
|
+
}
|
|
425
|
+
function consumeNormalQuote(line, index, state) {
|
|
426
|
+
if (!state.inString) return { advance: 0, handled: false };
|
|
427
|
+
const ch = line[index];
|
|
428
|
+
if (ch === "\\") return { advance: 1, handled: true };
|
|
429
|
+
if (ch === state.inString) state.inString = null;
|
|
430
|
+
return { advance: 0, handled: true };
|
|
431
|
+
}
|
|
432
|
+
function applyQuoteOrBrace(ch, state) {
|
|
433
|
+
if (ch === '"' || ch === "'") {
|
|
434
|
+
state.inString = ch;
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (ch === "{") state.delta++;
|
|
438
|
+
if (ch === "}") state.delta--;
|
|
439
|
+
}
|
|
440
|
+
function stepActiveCodeToken(line, j, ext, state) {
|
|
441
|
+
const pair = line.slice(j, j + 2);
|
|
442
|
+
if (pair === "//") return { advance: 0, stop: true };
|
|
443
|
+
if (pair === "/*") {
|
|
383
444
|
state.inBlock = true;
|
|
384
445
|
return { advance: 1, stop: false };
|
|
385
446
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
447
|
+
const rawOpen = detectRawStringOpen(line, j, ext);
|
|
448
|
+
if (rawOpen) {
|
|
449
|
+
state.rawTerm = rawOpen.term;
|
|
450
|
+
return { advance: rawOpen.advance, stop: false };
|
|
389
451
|
}
|
|
390
|
-
state
|
|
452
|
+
applyQuoteOrBrace(line[j], state);
|
|
391
453
|
return { advance: 0, stop: false };
|
|
392
454
|
}
|
|
393
|
-
function scanBracesInLine(line, wasInBlock, ext) {
|
|
394
|
-
const state = { delta: 0, inBlock: wasInBlock, inString: null };
|
|
455
|
+
function scanBracesInLine(line, wasInBlock, wasRawTerm, ext) {
|
|
456
|
+
const state = { delta: 0, inBlock: wasInBlock, rawTerm: wasRawTerm, inString: null };
|
|
395
457
|
for (let j = 0; j < line.length; j++) {
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (skipped.handled) {
|
|
400
|
-
state.inBlock = skipped.inBlock;
|
|
401
|
-
state.inString = skipped.inString;
|
|
402
|
-
if (skipped.skip) j++;
|
|
458
|
+
const rawOrBlock = consumeRawOrBlock(line, j, state);
|
|
459
|
+
if (rawOrBlock.handled) {
|
|
460
|
+
j += rawOrBlock.advance;
|
|
403
461
|
continue;
|
|
404
462
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
463
|
+
const quoteStep = consumeNormalQuote(line, j, state);
|
|
464
|
+
if (quoteStep.handled) {
|
|
465
|
+
j += quoteStep.advance;
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const codeStep = stepActiveCodeToken(line, j, ext, state);
|
|
469
|
+
if (codeStep.stop) break;
|
|
470
|
+
j += codeStep.advance;
|
|
409
471
|
}
|
|
410
|
-
return { delta: state.delta, nowInBlock: state.inBlock };
|
|
472
|
+
return { delta: state.delta, nowInBlock: state.inBlock, nowRawTerm: state.rawTerm };
|
|
411
473
|
}
|
|
412
474
|
function buildMetricsFromBlocks(blocks, content, filePath) {
|
|
413
475
|
if (blocks.length === 0) {
|
|
@@ -431,11 +493,20 @@ function buildMetricsFromBlocks(blocks, content, filePath) {
|
|
|
431
493
|
}
|
|
432
494
|
function detectPolyglotSmells(body) {
|
|
433
495
|
const smells = [];
|
|
434
|
-
if (/#\s*noqa|#\s*type:\s*ignore|@SuppressWarnings/.test(body)) {
|
|
496
|
+
if (/#\s*noqa|#\s*type:\s*ignore|@SuppressWarnings|#pragma\s+warning\s+disable/.test(body)) {
|
|
435
497
|
smells.push("Supresi\xF3n no autorizada de linter");
|
|
436
498
|
}
|
|
437
499
|
return smells;
|
|
438
500
|
}
|
|
501
|
+
function stripStringsAndComments(body, ext) {
|
|
502
|
+
let cleaned = body;
|
|
503
|
+
if (ext === ".py") {
|
|
504
|
+
cleaned = cleaned.replace(/"""[\s\S]*?"""|'''[\s\S]*?'''/g, '""').replace(/#.*/g, "");
|
|
505
|
+
} else {
|
|
506
|
+
cleaned = cleaned.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "").replace(/r(#+)"[\s\S]*?"\1/g, '""').replace(/R"([a-zA-Z0-9_]*)\([\s\S]*?\)\1"/g, '""').replace(/\$*"{3,}[\s\S]*?"{3,}/g, '""').replace(/`[\s\S]*?`/g, '""');
|
|
507
|
+
}
|
|
508
|
+
return cleaned.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, '""');
|
|
509
|
+
}
|
|
439
510
|
function computeHalsteadMI(body, cyclomatic, loc, commentCount) {
|
|
440
511
|
const cleanText = body.replace(/\/\*[\s\S]*?\*\/|\/\/.*|#.*/g, "");
|
|
441
512
|
const tokens = cleanText.split(/[\s,;().{}[\]=+\-*/<>!&|:]+/).filter((t) => t.length > 0);
|
|
@@ -460,9 +531,11 @@ function countCommentsAndLoc(rawLines, isPy) {
|
|
|
460
531
|
function calculatePolyglotMetrics(body, name, filePath, startLine) {
|
|
461
532
|
const ext = path4__namespace.extname(filePath).toLowerCase();
|
|
462
533
|
const rawLines = body.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
463
|
-
const { loc, commentCount
|
|
464
|
-
const
|
|
465
|
-
const
|
|
534
|
+
const { loc, commentCount } = countCommentsAndLoc(rawLines, ext === ".py");
|
|
535
|
+
const strippedBody = stripStringsAndComments(body, ext);
|
|
536
|
+
const strippedLines = strippedBody.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
537
|
+
const cyclomatic = computePolyglotCyclomatic(strippedBody, ext);
|
|
538
|
+
const cognitive = computePolyglotCognitive(strippedLines, ext === ".py");
|
|
466
539
|
const maintainability = computeHalsteadMI(body, cyclomatic, loc, commentCount);
|
|
467
540
|
return {
|
|
468
541
|
functionName: name,
|
|
@@ -475,40 +548,60 @@ function calculatePolyglotMetrics(body, name, filePath, startLine) {
|
|
|
475
548
|
line: startLine ? startLine + 1 : 1
|
|
476
549
|
};
|
|
477
550
|
}
|
|
478
|
-
function
|
|
479
|
-
let regex;
|
|
551
|
+
function getCyclomaticPattern(ext) {
|
|
480
552
|
if (ext === ".py") {
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
553
|
+
return /\b(if|elif|for|while|except|case)\b|\b(and|or)\b/g;
|
|
554
|
+
}
|
|
555
|
+
if (ext === ".go") {
|
|
556
|
+
return /\b(if|for|case|select)\b|&&|\|\|/g;
|
|
557
|
+
}
|
|
558
|
+
if (ext === ".rs") {
|
|
559
|
+
return /\b(if|for|while|match)\b|&&|\|\||\?/g;
|
|
560
|
+
}
|
|
561
|
+
if (ext === ".cs") {
|
|
562
|
+
return /\b(if|else\s+if|for|foreach|while|catch|case|when)\b|\?\?|\?\.(?=[a-zA-Z_])|&&|\|\|/g;
|
|
488
563
|
}
|
|
489
|
-
|
|
490
|
-
|
|
564
|
+
if (ext === ".java") {
|
|
565
|
+
return /\b(if|else\s+if|for|while|catch|case|when)\b|&&|\|\|/g;
|
|
566
|
+
}
|
|
567
|
+
return /\b(if|else\s+if|for|while|catch|case)\b|\?|&&|\|\|/g;
|
|
568
|
+
}
|
|
569
|
+
function computePolyglotCyclomatic(strippedBody, ext) {
|
|
570
|
+
const regex = getCyclomaticPattern(ext);
|
|
571
|
+
const matches = strippedBody.match(regex);
|
|
491
572
|
return 1 + (matches ? matches.length : 0);
|
|
492
573
|
}
|
|
493
|
-
|
|
574
|
+
var PY_COGNITIVE_RE = /\b(if|elif|for|while|except|match)\b/;
|
|
575
|
+
var BRACE_COGNITIVE_RE = /\b(if|for|foreach|while|catch|match|select|switch)\b/;
|
|
576
|
+
function computePythonCognitive(lines) {
|
|
577
|
+
let cognitive = 0;
|
|
578
|
+
const baseIndent = lines.length > 0 ? lines[0].length - lines[0].trimStart().length : 0;
|
|
579
|
+
for (let i = 1; i < lines.length; i++) {
|
|
580
|
+
const line = lines[i];
|
|
581
|
+
if (PY_COGNITIVE_RE.test(line)) {
|
|
582
|
+
const relIndent = Math.max(0, line.length - line.trimStart().length - baseIndent - 4);
|
|
583
|
+
cognitive += 1 + Math.floor(relIndent / 4);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return cognitive;
|
|
587
|
+
}
|
|
588
|
+
function computeBraceCognitive(lines) {
|
|
494
589
|
let cognitive = 0;
|
|
495
590
|
let nesting = 0;
|
|
496
|
-
for (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
cognitive += 1 + Math.floor(indent / 4);
|
|
501
|
-
}
|
|
502
|
-
} else {
|
|
503
|
-
if (line.includes("{")) nesting++;
|
|
504
|
-
if (line.includes("}")) nesting = Math.max(0, nesting - 1);
|
|
505
|
-
if (/\b(if|for|while|catch|match|select)\b/.test(line)) {
|
|
506
|
-
cognitive += 1 + nesting;
|
|
507
|
-
}
|
|
591
|
+
for (let i = 1; i < lines.length; i++) {
|
|
592
|
+
const line = lines[i];
|
|
593
|
+
if (BRACE_COGNITIVE_RE.test(line)) {
|
|
594
|
+
cognitive += 1 + nesting;
|
|
508
595
|
}
|
|
596
|
+
const opens = (line.match(/\{/g) || []).length;
|
|
597
|
+
const closes = (line.match(/\}/g) || []).length;
|
|
598
|
+
nesting = Math.max(0, nesting + opens - closes);
|
|
509
599
|
}
|
|
510
600
|
return cognitive;
|
|
511
601
|
}
|
|
602
|
+
function computePolyglotCognitive(lines, isPy) {
|
|
603
|
+
return isPy ? computePythonCognitive(lines) : computeBraceCognitive(lines);
|
|
604
|
+
}
|
|
512
605
|
function walkMdFiles(dir, fileList = [], excludeDirs = [
|
|
513
606
|
"node_modules",
|
|
514
607
|
".git",
|
|
@@ -1092,9 +1185,54 @@ function collectTestCorpus(rootDir, allMdFiles) {
|
|
|
1092
1185
|
}
|
|
1093
1186
|
return { gherkinSources, combinedTests: testContents.join("\n") };
|
|
1094
1187
|
}
|
|
1095
|
-
function
|
|
1188
|
+
function isCucumberScenarioPassed(element) {
|
|
1189
|
+
const steps = Array.isArray(element.steps) ? element.steps : [];
|
|
1190
|
+
if (steps.length === 0) return false;
|
|
1191
|
+
return steps.every((s) => {
|
|
1192
|
+
const res = s.result;
|
|
1193
|
+
return res && res.status === "passed";
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
function countCucumberScenarios(raw) {
|
|
1197
|
+
let passed = 0;
|
|
1198
|
+
let failed = 0;
|
|
1199
|
+
for (const feat of raw) {
|
|
1200
|
+
const elements = Array.isArray(feat.elements) ? feat.elements : [];
|
|
1201
|
+
for (const el of elements) {
|
|
1202
|
+
if (isCucumberScenarioPassed(el)) passed++;
|
|
1203
|
+
else failed++;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return { passed, failed };
|
|
1207
|
+
}
|
|
1208
|
+
function parseCucumberReportFile(reportPath) {
|
|
1209
|
+
if (!fs7__namespace.existsSync(reportPath)) return void 0;
|
|
1210
|
+
try {
|
|
1211
|
+
const raw = JSON.parse(fs7__namespace.readFileSync(reportPath, "utf-8"));
|
|
1212
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1213
|
+
const { passed, failed } = countCucumberScenarios(raw);
|
|
1214
|
+
return {
|
|
1215
|
+
evaluatedFeatures: raw.length,
|
|
1216
|
+
passedScenarios: passed,
|
|
1217
|
+
failedScenarios: failed,
|
|
1218
|
+
passed: passed + failed > 0 && failed === 0
|
|
1219
|
+
};
|
|
1220
|
+
} catch {
|
|
1221
|
+
return void 0;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
function resolveCucumberReport(rootDir, options) {
|
|
1225
|
+
const candidate = options.cucumberReportPath ? path4__namespace.isAbsolute(options.cucumberReportPath) ? options.cucumberReportPath : path4__namespace.join(rootDir, options.cucumberReportPath) : path4__namespace.join(rootDir, "reports", "cucumber-report.json");
|
|
1226
|
+
const parsed = parseCucumberReportFile(candidate);
|
|
1227
|
+
if (parsed) return parsed;
|
|
1228
|
+
if (options.enforceCucumberExecution) {
|
|
1229
|
+
return { evaluatedFeatures: 0, passedScenarios: 0, failedScenarios: 1, passed: false };
|
|
1230
|
+
}
|
|
1231
|
+
return void 0;
|
|
1232
|
+
}
|
|
1233
|
+
function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, taskErrors, ucErrors, rootDir, options) {
|
|
1096
1234
|
return {
|
|
1097
|
-
success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0,
|
|
1235
|
+
success: reqErrors === 0 && taskErrors === 0 && ucErrors === 0 && (!resolveCucumberReport(rootDir, options) || resolveCucumberReport(rootDir, options).passed),
|
|
1098
1236
|
totalRequirements: reqAudit.length,
|
|
1099
1237
|
passedRequirements: reqAudit.length - reqErrors,
|
|
1100
1238
|
failedRequirements: reqErrors,
|
|
@@ -1105,6 +1243,7 @@ function assembleCoverageResult(reqAudit, taskAudit, useCaseFlows, reqErrors, ta
|
|
|
1105
1243
|
tasks: taskAudit,
|
|
1106
1244
|
useCaseFlows,
|
|
1107
1245
|
failedUseCaseFlows: ucErrors,
|
|
1246
|
+
...resolveCucumberReport(rootDir, options) ? { cucumberExecution: resolveCucumberReport(rootDir, options) } : {},
|
|
1108
1247
|
reportMarkdown: generateTestingCoverageReportMarkdown(reqAudit, taskAudit, reqErrors, taskErrors)
|
|
1109
1248
|
};
|
|
1110
1249
|
}
|
|
@@ -1156,7 +1295,9 @@ function verifyTestingCoverage(options = {}) {
|
|
|
1156
1295
|
state.useCaseFlows,
|
|
1157
1296
|
state.reqErrors,
|
|
1158
1297
|
state.taskErrors,
|
|
1159
|
-
state.ucErrors
|
|
1298
|
+
state.ucErrors,
|
|
1299
|
+
rootDir,
|
|
1300
|
+
options
|
|
1160
1301
|
);
|
|
1161
1302
|
}
|
|
1162
1303
|
|
|
@@ -5532,6 +5673,277 @@ function verifyTasksGovernance(options = {}) {
|
|
|
5532
5673
|
);
|
|
5533
5674
|
return assembleGovernanceResult(data, reportMarkdown);
|
|
5534
5675
|
}
|
|
5676
|
+
var BddStepRegistry = class {
|
|
5677
|
+
definitions = [];
|
|
5678
|
+
register(pattern, handler) {
|
|
5679
|
+
this.definitions.push({ pattern, handler });
|
|
5680
|
+
return this;
|
|
5681
|
+
}
|
|
5682
|
+
given(pattern, handler) {
|
|
5683
|
+
return this.register(pattern, handler);
|
|
5684
|
+
}
|
|
5685
|
+
when(pattern, handler) {
|
|
5686
|
+
return this.register(pattern, handler);
|
|
5687
|
+
}
|
|
5688
|
+
then(pattern, handler) {
|
|
5689
|
+
return this.register(pattern, handler);
|
|
5690
|
+
}
|
|
5691
|
+
getDefinitions() {
|
|
5692
|
+
return this.definitions;
|
|
5693
|
+
}
|
|
5694
|
+
};
|
|
5695
|
+
var STEP_PREFIX_RE = /^(Given|When|Then|And|But|Dado|Cuando|Entonces|Y|Pero)\s+(.+)$/i;
|
|
5696
|
+
var SCENARIO_HEADER_RE = /^(Scenario|Escenario|Scenario Outline|Esquema del escenario):\s*(.+)$/i;
|
|
5697
|
+
function parseTableRowCells(line) {
|
|
5698
|
+
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
5699
|
+
}
|
|
5700
|
+
function attachStepTableRow(lastStep, cells) {
|
|
5701
|
+
if (!lastStep || cells.length < 2) return;
|
|
5702
|
+
if (!lastStep.table) lastStep.table = {};
|
|
5703
|
+
lastStep.table[cells[0]] = cells[1];
|
|
5704
|
+
}
|
|
5705
|
+
function attachExampleRow(scenario, headers, cells) {
|
|
5706
|
+
if (!scenario) return headers;
|
|
5707
|
+
if (headers.length === 0) return cells;
|
|
5708
|
+
const row = {};
|
|
5709
|
+
for (let i = 0; i < headers.length; i++) {
|
|
5710
|
+
row[headers[i]] = cells[i] ?? "";
|
|
5711
|
+
}
|
|
5712
|
+
scenario.examples.push(row);
|
|
5713
|
+
return headers;
|
|
5714
|
+
}
|
|
5715
|
+
function createInitialParseState() {
|
|
5716
|
+
return {
|
|
5717
|
+
featureName: "Unnamed Feature",
|
|
5718
|
+
featureTags: [],
|
|
5719
|
+
pendingTags: [],
|
|
5720
|
+
backgroundSteps: [],
|
|
5721
|
+
scenarios: [],
|
|
5722
|
+
currentScenario: null,
|
|
5723
|
+
inBackground: false,
|
|
5724
|
+
inExamples: false,
|
|
5725
|
+
exampleHeaders: []
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
function handleStructuralHeaderLine(line, state) {
|
|
5729
|
+
if (/^(Feature|Caracter[ií]stica):/i.test(line)) {
|
|
5730
|
+
state.featureName = line.replace(/^(Feature|Caracter[ií]stica):\s*/i, "").trim();
|
|
5731
|
+
state.featureTags.push(...state.pendingTags);
|
|
5732
|
+
state.pendingTags = [];
|
|
5733
|
+
return true;
|
|
5734
|
+
}
|
|
5735
|
+
if (/^(Background|Antecedentes):/i.test(line)) {
|
|
5736
|
+
state.inBackground = true;
|
|
5737
|
+
state.inExamples = false;
|
|
5738
|
+
state.currentScenario = null;
|
|
5739
|
+
return true;
|
|
5740
|
+
}
|
|
5741
|
+
if (/^(Examples|Ejemplos):/i.test(line)) {
|
|
5742
|
+
state.inExamples = true;
|
|
5743
|
+
state.exampleHeaders = [];
|
|
5744
|
+
return true;
|
|
5745
|
+
}
|
|
5746
|
+
return false;
|
|
5747
|
+
}
|
|
5748
|
+
function startScenarioBlock(scenMatch, state) {
|
|
5749
|
+
state.inBackground = false;
|
|
5750
|
+
state.inExamples = false;
|
|
5751
|
+
const block = {
|
|
5752
|
+
name: scenMatch[2].trim(),
|
|
5753
|
+
tags: [...state.featureTags, ...state.pendingTags],
|
|
5754
|
+
isOutline: /outline|esquema/i.test(scenMatch[1]),
|
|
5755
|
+
steps: [],
|
|
5756
|
+
examples: []
|
|
5757
|
+
};
|
|
5758
|
+
state.pendingTags = [];
|
|
5759
|
+
state.scenarios.push(block);
|
|
5760
|
+
state.currentScenario = block;
|
|
5761
|
+
}
|
|
5762
|
+
function appendParsedStep(stepMatch, state) {
|
|
5763
|
+
const step = { keyword: stepMatch[1], text: stepMatch[2].trim() };
|
|
5764
|
+
if (state.inBackground) {
|
|
5765
|
+
state.backgroundSteps.push(step);
|
|
5766
|
+
} else if (state.currentScenario) {
|
|
5767
|
+
state.currentScenario.steps.push(step);
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
function handleScenarioOrStepLine(line, state) {
|
|
5771
|
+
const scenMatch = line.match(SCENARIO_HEADER_RE);
|
|
5772
|
+
if (scenMatch) {
|
|
5773
|
+
startScenarioBlock(scenMatch, state);
|
|
5774
|
+
return;
|
|
5775
|
+
}
|
|
5776
|
+
const stepMatch = line.match(STEP_PREFIX_RE);
|
|
5777
|
+
if (stepMatch) {
|
|
5778
|
+
appendParsedStep(stepMatch, state);
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
function processFeatureLine(rawLine, state) {
|
|
5782
|
+
const line = rawLine.trim();
|
|
5783
|
+
if (!line || line.startsWith("#")) return;
|
|
5784
|
+
if (line.startsWith("@")) {
|
|
5785
|
+
const tags = line.split(/\s+/).filter((t) => t.startsWith("@"));
|
|
5786
|
+
state.pendingTags.push(...tags);
|
|
5787
|
+
return;
|
|
5788
|
+
}
|
|
5789
|
+
if (line.startsWith("|")) {
|
|
5790
|
+
const cells = parseTableRowCells(line);
|
|
5791
|
+
if (state.inExamples) {
|
|
5792
|
+
state.exampleHeaders = attachExampleRow(state.currentScenario, state.exampleHeaders, cells);
|
|
5793
|
+
} else {
|
|
5794
|
+
const targetList = state.inBackground ? state.backgroundSteps : state.currentScenario?.steps || [];
|
|
5795
|
+
attachStepTableRow(targetList[targetList.length - 1], cells);
|
|
5796
|
+
}
|
|
5797
|
+
return;
|
|
5798
|
+
}
|
|
5799
|
+
if (!handleStructuralHeaderLine(line, state)) {
|
|
5800
|
+
handleScenarioOrStepLine(line, state);
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
function parseFeatureDocument(content) {
|
|
5804
|
+
const state = createInitialParseState();
|
|
5805
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
5806
|
+
processFeatureLine(rawLine, state);
|
|
5807
|
+
}
|
|
5808
|
+
return {
|
|
5809
|
+
featureName: state.featureName,
|
|
5810
|
+
tags: state.featureTags,
|
|
5811
|
+
backgroundSteps: state.backgroundSteps,
|
|
5812
|
+
scenarios: state.scenarios
|
|
5813
|
+
};
|
|
5814
|
+
}
|
|
5815
|
+
function interpolatePlaceholders(text, row) {
|
|
5816
|
+
let out = text;
|
|
5817
|
+
for (const [k, v] of Object.entries(row)) {
|
|
5818
|
+
out = out.replace(new RegExp(`<${k}>`, "g"), v);
|
|
5819
|
+
}
|
|
5820
|
+
return out;
|
|
5821
|
+
}
|
|
5822
|
+
function expandScenarioOutlines(scenarios) {
|
|
5823
|
+
const expanded = [];
|
|
5824
|
+
for (const sc of scenarios) {
|
|
5825
|
+
if (!sc.isOutline || sc.examples.length === 0) {
|
|
5826
|
+
expanded.push(sc);
|
|
5827
|
+
continue;
|
|
5828
|
+
}
|
|
5829
|
+
sc.examples.forEach((row, idx) => {
|
|
5830
|
+
expanded.push({
|
|
5831
|
+
name: `${sc.name} (Example #${idx + 1})`,
|
|
5832
|
+
tags: sc.tags,
|
|
5833
|
+
isOutline: false,
|
|
5834
|
+
steps: sc.steps.map((st) => ({
|
|
5835
|
+
keyword: st.keyword,
|
|
5836
|
+
text: interpolatePlaceholders(st.text, row),
|
|
5837
|
+
table: st.table
|
|
5838
|
+
})),
|
|
5839
|
+
examples: []
|
|
5840
|
+
});
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5843
|
+
return expanded;
|
|
5844
|
+
}
|
|
5845
|
+
function matchStepDefinition(text, definitions) {
|
|
5846
|
+
for (const entry of definitions) {
|
|
5847
|
+
if (typeof entry.pattern === "string") {
|
|
5848
|
+
if (entry.pattern === text) return { entry, args: [] };
|
|
5849
|
+
} else {
|
|
5850
|
+
const m = text.match(entry.pattern);
|
|
5851
|
+
if (m) return { entry, args: m.slice(1) };
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
return null;
|
|
5855
|
+
}
|
|
5856
|
+
async function executeSingleStep(step, definitions) {
|
|
5857
|
+
const matched = matchStepDefinition(step.text, definitions);
|
|
5858
|
+
if (!matched) {
|
|
5859
|
+
return { keyword: step.keyword, text: step.text, status: "undefined", durationMs: 0 };
|
|
5860
|
+
}
|
|
5861
|
+
const start = Date.now();
|
|
5862
|
+
try {
|
|
5863
|
+
await matched.entry.handler(matched.args, step.table);
|
|
5864
|
+
return { keyword: step.keyword, text: step.text, status: "passed", durationMs: Date.now() - start };
|
|
5865
|
+
} catch (err) {
|
|
5866
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5867
|
+
return {
|
|
5868
|
+
keyword: step.keyword,
|
|
5869
|
+
text: step.text,
|
|
5870
|
+
status: "failed",
|
|
5871
|
+
durationMs: Date.now() - start,
|
|
5872
|
+
errorMessage: msg
|
|
5873
|
+
};
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5876
|
+
async function executeScenarioSteps(allSteps, definitions) {
|
|
5877
|
+
const stepResults = [];
|
|
5878
|
+
let status = "passed";
|
|
5879
|
+
for (const step of allSteps) {
|
|
5880
|
+
if (status !== "passed") {
|
|
5881
|
+
stepResults.push({ keyword: step.keyword, text: step.text, status: "skipped", durationMs: 0 });
|
|
5882
|
+
continue;
|
|
5883
|
+
}
|
|
5884
|
+
const res = await executeSingleStep(step, definitions);
|
|
5885
|
+
stepResults.push(res);
|
|
5886
|
+
if (res.status === "failed" || res.status === "undefined") {
|
|
5887
|
+
status = res.status;
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
return { stepResults, status };
|
|
5891
|
+
}
|
|
5892
|
+
async function runAllExpandedScenarios(parsed, definitions) {
|
|
5893
|
+
const expanded = expandScenarioOutlines(parsed.scenarios);
|
|
5894
|
+
const out = [];
|
|
5895
|
+
for (const sc of expanded) {
|
|
5896
|
+
const combinedSteps = [...parsed.backgroundSteps, ...sc.steps];
|
|
5897
|
+
const { stepResults, status } = await executeScenarioSteps(combinedSteps, definitions);
|
|
5898
|
+
out.push({ name: sc.name, tags: sc.tags, status, steps: stepResults });
|
|
5899
|
+
}
|
|
5900
|
+
return out;
|
|
5901
|
+
}
|
|
5902
|
+
async function executeBddFeature(featurePath, registry, options = {}) {
|
|
5903
|
+
const rootDir = options.rootDir || process.cwd();
|
|
5904
|
+
const fullPath = path4__namespace.isAbsolute(featurePath) ? featurePath : path4__namespace.join(rootDir, featurePath);
|
|
5905
|
+
const parsed = parseFeatureDocument(fs7__namespace.readFileSync(fullPath, "utf-8"));
|
|
5906
|
+
const scenarios = await runAllExpandedScenarios(parsed, registry.getDefinitions());
|
|
5907
|
+
const report = {
|
|
5908
|
+
uri: path4__namespace.relative(rootDir, fullPath).replace(/\\/g, "/"),
|
|
5909
|
+
featureName: parsed.featureName,
|
|
5910
|
+
tags: parsed.tags,
|
|
5911
|
+
scenarios,
|
|
5912
|
+
passed: scenarios.length > 0 && scenarios.every((s) => s.status === "passed")
|
|
5913
|
+
};
|
|
5914
|
+
if (options.writeReportPath) writeCucumberJsonReport([report], options.writeReportPath);
|
|
5915
|
+
return report;
|
|
5916
|
+
}
|
|
5917
|
+
function mapStepToCucumberJson(st) {
|
|
5918
|
+
return {
|
|
5919
|
+
keyword: `${st.keyword} `,
|
|
5920
|
+
name: st.text,
|
|
5921
|
+
result: {
|
|
5922
|
+
status: st.status,
|
|
5923
|
+
duration: st.durationMs * 1e6,
|
|
5924
|
+
...st.errorMessage ? { error_message: st.errorMessage } : {}
|
|
5925
|
+
}
|
|
5926
|
+
};
|
|
5927
|
+
}
|
|
5928
|
+
function mapFeatureToCucumberJson(r) {
|
|
5929
|
+
return {
|
|
5930
|
+
uri: r.uri,
|
|
5931
|
+
name: r.featureName,
|
|
5932
|
+
tags: r.tags.map((t) => ({ name: t })),
|
|
5933
|
+
elements: r.scenarios.map((sc) => ({
|
|
5934
|
+
type: "scenario",
|
|
5935
|
+
name: sc.name,
|
|
5936
|
+
tags: sc.tags.map((t) => ({ name: t })),
|
|
5937
|
+
steps: sc.steps.map(mapStepToCucumberJson)
|
|
5938
|
+
}))
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5941
|
+
function writeCucumberJsonReport(reports, outputPath) {
|
|
5942
|
+
const dir = path4__namespace.dirname(outputPath);
|
|
5943
|
+
if (!fs7__namespace.existsSync(dir)) fs7__namespace.mkdirSync(dir, { recursive: true });
|
|
5944
|
+
const payload = JSON.stringify(reports.map(mapFeatureToCucumberJson), null, 2);
|
|
5945
|
+
fs7__namespace.writeFileSync(outputPath, payload, "utf-8");
|
|
5946
|
+
}
|
|
5535
5947
|
function isToolAvailable(toolName) {
|
|
5536
5948
|
try {
|
|
5537
5949
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
@@ -6172,7 +6584,7 @@ function resolveProjectIdentity(options) {
|
|
|
6172
6584
|
function buildBomMetadata(name, version) {
|
|
6173
6585
|
return {
|
|
6174
6586
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6175
|
-
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.
|
|
6587
|
+
tools: [{ vendor: "AI-SDLC", name: "@aisdlc/core", version: "2.4.0" }],
|
|
6176
6588
|
component: { type: "application", name, version }
|
|
6177
6589
|
};
|
|
6178
6590
|
}
|
|
@@ -16348,6 +16760,7 @@ function sanitizeDlpPayload(payload, policy = createDefaultDlpPolicy()) {
|
|
|
16348
16760
|
|
|
16349
16761
|
exports.ALL_ARCHITECTURE_TEMPLATES = ALL_ARCHITECTURE_TEMPLATES;
|
|
16350
16762
|
exports.BRANCH_PATTERNS = BRANCH_PATTERNS;
|
|
16763
|
+
exports.BddStepRegistry = BddStepRegistry;
|
|
16351
16764
|
exports.DEFAULT_DLP_RULES = DEFAULT_DLP_RULES;
|
|
16352
16765
|
exports.DEFAULT_EXCLUDED_PATTERNS = DEFAULT_EXCLUDED_PATTERNS;
|
|
16353
16766
|
exports.DEFAULT_EXCLUDES = DEFAULT_EXCLUDES;
|
|
@@ -16523,6 +16936,7 @@ exports.emitCiOutput = emitCiOutput;
|
|
|
16523
16936
|
exports.evaluateCompoundLicense = evaluateCompoundLicense;
|
|
16524
16937
|
exports.evaluateSingleDependency = evaluateSingleDependency;
|
|
16525
16938
|
exports.evaluateVulnerabilityThresholds = evaluateVulnerabilityThresholds;
|
|
16939
|
+
exports.executeBddFeature = executeBddFeature;
|
|
16526
16940
|
exports.extractBaselineEntriesFromResults = extractBaselineEntriesFromResults;
|
|
16527
16941
|
exports.extractCitations = extractCitations;
|
|
16528
16942
|
exports.extractCommitKpisFromRange = extractCommitKpisFromRange;
|
|
@@ -16588,6 +17002,7 @@ exports.normalizeSpdxLicense = normalizeSpdxLicense;
|
|
|
16588
17002
|
exports.normalizeUri = normalizeUri;
|
|
16589
17003
|
exports.parseCommitTrailers = parseCommitTrailers;
|
|
16590
17004
|
exports.parseDlpPolicy = parseDlpPolicy;
|
|
17005
|
+
exports.parseFeatureDocument = parseFeatureDocument;
|
|
16591
17006
|
exports.parseFrontmatter = parseFrontmatter;
|
|
16592
17007
|
exports.parseFrontmatterId = parseFrontmatterId;
|
|
16593
17008
|
exports.parseGenericDoc = parseGenericDoc;
|
|
@@ -16674,6 +17089,7 @@ exports.walkMdFiles = walkMdFiles;
|
|
|
16674
17089
|
exports.walkProjectFiles = walkProjectFiles;
|
|
16675
17090
|
exports.walkSourceFiles = walkSourceFiles;
|
|
16676
17091
|
exports.walkTaskFiles = walkTaskFiles;
|
|
17092
|
+
exports.writeCucumberJsonReport = writeCucumberJsonReport;
|
|
16677
17093
|
exports.writeCycloneDxSbom = writeCycloneDxSbom;
|
|
16678
17094
|
exports.writeQualityBaseline = writeQualityBaseline;
|
|
16679
17095
|
exports.writeReleaseKpiReport = writeReleaseKpiReport;
|