faf-taf-git 2.4.0 → 2.4.2
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/CHANGELOG.md +23 -1
- package/README.md +1 -1
- package/RECEIPT-FORMAT.md +1 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -1
- package/dist/cli.js.map +1 -1
- package/package.json +15 -2
- package/project.faf +32 -242
- package/.faf-dna +0 -95
- package/.fafignore +0 -41
- package/.github/dependabot.yml +0 -10
- package/.github/workflows/ci.yml +0 -53
- package/.github/workflows/claude-code-review.yml +0 -36
- package/.github/workflows/claude.yml +0 -39
- package/.github/workflows/homebrew.yml +0 -19
- package/.taf +0 -98
- package/CLAUDE.md +0 -38
- package/CODE_OF_CONDUCT.md +0 -21
- package/CONTRIBUTING.md +0 -289
- package/SECURITY.md +0 -19
- package/action.yml +0 -50
- package/agent.fafa +0 -53
- package/badge.svg +0 -21
- package/dist/bundle/index.js +0 -36554
- package/dist/index.js +0 -42827
- package/dist/index.js.map +0 -1
- package/dist/licenses.txt +0 -126
- package/dist/sourcemap-register.js +0 -1
- package/docs/RELEASE-NOTES.md +0 -200
- package/docs/USER-GUIDE.md +0 -301
- package/jest.config.js +0 -13
- package/src/badge.ts +0 -138
- package/src/cli.ts +0 -297
- package/src/index.ts +0 -201
- package/src/parsers/bun.ts +0 -103
- package/src/parsers/index.ts +0 -50
- package/src/parsers/jest.ts +0 -104
- package/src/parsers/junit.ts +0 -110
- package/src/parsers/vitest.ts +0 -65
- package/src/parsers/wjttc.ts +0 -160
- package/src/taf-core.ts +0 -139
- package/test-mcp.ts +0 -83
- package/test-standalone.ts +0 -87
- package/tests/action-bundle.test.ts +0 -34
- package/tests/action-parse-error.test.ts +0 -47
- package/tests/badge.test.ts +0 -260
- package/tests/bun-parser.test.ts +0 -173
- package/tests/jest-parser.test.ts +0 -122
- package/tests/junit-parser.test.ts +0 -109
- package/tests/vitest-parser.test.ts +0 -112
- package/tests/wjttc-parser.test.ts +0 -116
- package/tests/wjttc-taf.test.ts +0 -470
- package/tsconfig.json +0 -18
package/src/parsers/jest.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Jest output parser
|
|
3
|
-
*
|
|
4
|
-
* Extracts test counts from Jest CLI output
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface TestResults {
|
|
8
|
-
total: number;
|
|
9
|
-
passed: number;
|
|
10
|
-
failed: number;
|
|
11
|
-
skipped?: number;
|
|
12
|
-
result: 'PASSED' | 'FAILED' | 'IMPROVED' | 'DEGRADED';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Parse Jest output to extract test results
|
|
17
|
-
*
|
|
18
|
-
* Jest output formats:
|
|
19
|
-
* - "Tests: 1 failed, 172 passed, 173 total"
|
|
20
|
-
* - "Tests: 173 passed, 173 total"
|
|
21
|
-
* - "Test Suites: 2 failed, 16 passed, 18 total"
|
|
22
|
-
* - "Tests: 9 skipped, 799 passed, 808 total" (with padding)
|
|
23
|
-
*/
|
|
24
|
-
export function parseJestOutput(output: string): TestResults | null {
|
|
25
|
-
// Strip ANSI color codes and other control characters
|
|
26
|
-
// eslint-disable-next-line no-control-regex
|
|
27
|
-
let cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
|
28
|
-
// Also strip carriage returns and normalize line endings
|
|
29
|
-
cleanOutput = cleanOutput.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
30
|
-
|
|
31
|
-
// Find the Jest test summary line
|
|
32
|
-
// CRITICAL: Must match "Tests:" line that contains "total" to avoid matching
|
|
33
|
-
// test descriptions like "Brake Tests: Critical dogfooding scenarios"
|
|
34
|
-
const testSummaryPattern = /Tests:\s+(.+?\d+\s+total)/im;
|
|
35
|
-
const testLineMatch = cleanOutput.match(testSummaryPattern);
|
|
36
|
-
|
|
37
|
-
if (!testLineMatch || !testLineMatch[1]) {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const testLine = testLineMatch[1];
|
|
42
|
-
|
|
43
|
-
// Extract counts using flexible patterns
|
|
44
|
-
let total = 0;
|
|
45
|
-
let passed = 0;
|
|
46
|
-
let failed = 0;
|
|
47
|
-
let skipped = 0;
|
|
48
|
-
|
|
49
|
-
// Parse numbers - use global search on the whole output near the Tests: line
|
|
50
|
-
const totalMatch = testLine.match(/(\d+)\s+total/i);
|
|
51
|
-
if (totalMatch) {
|
|
52
|
-
total = parseInt(totalMatch[1], 10);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const passedMatch = testLine.match(/(\d+)\s+passed/i);
|
|
56
|
-
if (passedMatch) {
|
|
57
|
-
passed = parseInt(passedMatch[1], 10);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const failedMatch = testLine.match(/(\d+)\s+failed/i);
|
|
61
|
-
if (failedMatch) {
|
|
62
|
-
failed = parseInt(failedMatch[1], 10);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const skippedMatch = testLine.match(/(\d+)\s+skipped/i);
|
|
66
|
-
if (skippedMatch) {
|
|
67
|
-
skipped = parseInt(skippedMatch[1], 10);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Validate
|
|
71
|
-
if (total === 0) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Determine result
|
|
76
|
-
let result: 'PASSED' | 'FAILED' | 'IMPROVED' | 'DEGRADED' = 'PASSED';
|
|
77
|
-
if (failed > 0) {
|
|
78
|
-
result = 'FAILED';
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
total,
|
|
83
|
-
passed,
|
|
84
|
-
failed,
|
|
85
|
-
skipped: skipped > 0 ? skipped : undefined,
|
|
86
|
-
result,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Example Jest outputs for testing:
|
|
92
|
-
*
|
|
93
|
-
* All passing:
|
|
94
|
-
* "Tests: 173 passed, 173 total"
|
|
95
|
-
*
|
|
96
|
-
* Some failing:
|
|
97
|
-
* "Tests: 1 failed, 172 passed, 173 total"
|
|
98
|
-
*
|
|
99
|
-
* With skipped:
|
|
100
|
-
* "Tests: 1 failed, 2 skipped, 170 passed, 173 total"
|
|
101
|
-
*
|
|
102
|
-
* With padding (CI):
|
|
103
|
-
* "Tests: 9 skipped, 799 passed, 808 total"
|
|
104
|
-
*/
|
package/src/parsers/junit.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JUnit XML parser
|
|
3
|
-
*
|
|
4
|
-
* Extracts test counts from a JUnit XML report — the common format nearly every
|
|
5
|
-
* test runner can write: pytest (--junitxml), cargo (nextest, cargo2junit), Go
|
|
6
|
-
* (go-junit-report, gotestsum), Maven/Gradle, .NET, PHPUnit, Jest (jest-junit).
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { TestResults } from './jest';
|
|
10
|
-
|
|
11
|
-
const SUITE_TAG = /<(\/?)testsuite\b([^>]*?)(\/?)>/g;
|
|
12
|
-
|
|
13
|
-
/** Read one numeric attribute; undefined when the attribute is absent. */
|
|
14
|
-
function attr(attrs: string, name: string): number | undefined {
|
|
15
|
-
const match = attrs.match(new RegExp(`\\b${name}\\s*=\\s*["'](\\d+)["']`));
|
|
16
|
-
return match ? parseInt(match[1], 10) : undefined;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface Counts {
|
|
20
|
-
tests: number;
|
|
21
|
-
failed: number;
|
|
22
|
-
skipped: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Counts from a <testsuite> or <testsuites> attribute list, or null without a `tests` count. */
|
|
26
|
-
function countsFromAttrs(attrs: string): Counts | null {
|
|
27
|
-
const tests = attr(attrs, 'tests');
|
|
28
|
-
if (tests === undefined) return null;
|
|
29
|
-
return {
|
|
30
|
-
tests,
|
|
31
|
-
failed: (attr(attrs, 'failures') ?? 0) + (attr(attrs, 'errors') ?? 0),
|
|
32
|
-
skipped: (attr(attrs, 'skipped') ?? 0) + (attr(attrs, 'disabled') ?? 0),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Fallback when suites carry no counts: walk the <testcase> elements themselves. */
|
|
37
|
-
function countsFromTestcases(xml: string): Counts {
|
|
38
|
-
const counts: Counts = { tests: 0, failed: 0, skipped: 0 };
|
|
39
|
-
const testcase = /<testcase\b[^>]*?(?:\/>|>([\s\S]*?)<\/testcase>)/g;
|
|
40
|
-
let match: RegExpExecArray | null;
|
|
41
|
-
while ((match = testcase.exec(xml)) !== null) {
|
|
42
|
-
counts.tests++;
|
|
43
|
-
const body = match[1] ?? '';
|
|
44
|
-
if (/<(failure|error)\b/.test(body)) counts.failed++;
|
|
45
|
-
else if (/<skipped\b/.test(body)) counts.skipped++;
|
|
46
|
-
}
|
|
47
|
-
return counts;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Parse a JUnit XML report to extract test results
|
|
52
|
-
*
|
|
53
|
-
* Handles:
|
|
54
|
-
* - a <testsuites> root that carries the totals (gotestsum, Maven)
|
|
55
|
-
* - a <testsuites> root without totals, around counted <testsuite>s (pytest)
|
|
56
|
-
* - a single <testsuite> root
|
|
57
|
-
* - nested <testsuite>s (only the outermost level is counted, never twice)
|
|
58
|
-
* - suites with no counts at all (the <testcase> elements are counted instead)
|
|
59
|
-
*
|
|
60
|
-
* failures + errors count as failed; skipped + disabled count as skipped.
|
|
61
|
-
*/
|
|
62
|
-
export function parseJunitOutput(output: string): TestResults | null {
|
|
63
|
-
const xml = output.replace(/<!--[\s\S]*?-->/g, '');
|
|
64
|
-
if (!/<testsuites?\b/.test(xml)) return null;
|
|
65
|
-
|
|
66
|
-
let counts: Counts | null = null;
|
|
67
|
-
|
|
68
|
-
const root = xml.match(/<testsuites\b([^>]*)>/);
|
|
69
|
-
if (root) counts = countsFromAttrs(root[1]);
|
|
70
|
-
|
|
71
|
-
if (!counts) {
|
|
72
|
-
// Sum the outermost <testsuite>s; a suite without counts sends us to the testcases.
|
|
73
|
-
let depth = 0;
|
|
74
|
-
let summed: Counts | null = { tests: 0, failed: 0, skipped: 0 };
|
|
75
|
-
let seen = 0;
|
|
76
|
-
let match: RegExpExecArray | null;
|
|
77
|
-
SUITE_TAG.lastIndex = 0;
|
|
78
|
-
while ((match = SUITE_TAG.exec(xml)) !== null) {
|
|
79
|
-
const [, closing, attrs, selfClosing] = match;
|
|
80
|
-
if (closing) {
|
|
81
|
-
depth--;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
if (depth === 0 && summed) {
|
|
85
|
-
seen++;
|
|
86
|
-
const suite = countsFromAttrs(attrs);
|
|
87
|
-
if (suite) {
|
|
88
|
-
summed.tests += suite.tests;
|
|
89
|
-
summed.failed += suite.failed;
|
|
90
|
-
summed.skipped += suite.skipped;
|
|
91
|
-
} else {
|
|
92
|
-
summed = null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (!selfClosing) depth++;
|
|
96
|
-
}
|
|
97
|
-
counts = seen > 0 && summed ? summed : countsFromTestcases(xml);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (counts.tests === 0) return null;
|
|
101
|
-
|
|
102
|
-
const passed = Math.max(0, counts.tests - counts.failed - counts.skipped);
|
|
103
|
-
return {
|
|
104
|
-
total: counts.tests,
|
|
105
|
-
passed,
|
|
106
|
-
failed: counts.failed,
|
|
107
|
-
skipped: counts.skipped > 0 ? counts.skipped : undefined,
|
|
108
|
-
result: counts.failed > 0 ? 'FAILED' : 'PASSED',
|
|
109
|
-
};
|
|
110
|
-
}
|
package/src/parsers/vitest.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vitest output parser
|
|
3
|
-
*
|
|
4
|
-
* Extracts test counts from Vitest CLI output
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { TestResults } from './jest';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Parse Vitest output to extract test results
|
|
11
|
-
*
|
|
12
|
-
* Vitest output formats:
|
|
13
|
-
* - " Tests 8 passed (8)"
|
|
14
|
-
* - " Tests 2 failed | 6 passed (8)"
|
|
15
|
-
* - " Tests 1 skipped | 7 passed (8)"
|
|
16
|
-
* - " Tests 1 failed | 2 skipped | 5 passed (8)"
|
|
17
|
-
*/
|
|
18
|
-
export function parseVitestOutput(output: string): TestResults | null {
|
|
19
|
-
// Strip ANSI color codes
|
|
20
|
-
// eslint-disable-next-line no-control-regex
|
|
21
|
-
let cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
|
|
22
|
-
cleanOutput = cleanOutput.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
23
|
-
|
|
24
|
-
// Vitest summary pattern: " Tests ... (N)" where N is total in parens
|
|
25
|
-
const testSummaryPattern = /^\s*Tests\s+(.+?\(\d+\))/m;
|
|
26
|
-
const testLineMatch = cleanOutput.match(testSummaryPattern);
|
|
27
|
-
|
|
28
|
-
if (!testLineMatch || !testLineMatch[1]) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const testLine = testLineMatch[1];
|
|
33
|
-
|
|
34
|
-
// Extract total from parentheses at end
|
|
35
|
-
const totalMatch = testLine.match(/\((\d+)\)\s*$/);
|
|
36
|
-
if (!totalMatch) return null;
|
|
37
|
-
const total = parseInt(totalMatch[1], 10);
|
|
38
|
-
if (total === 0) return null;
|
|
39
|
-
|
|
40
|
-
let passed = 0;
|
|
41
|
-
let failed = 0;
|
|
42
|
-
let skipped = 0;
|
|
43
|
-
|
|
44
|
-
const passedMatch = testLine.match(/(\d+)\s+passed/i);
|
|
45
|
-
if (passedMatch) passed = parseInt(passedMatch[1], 10);
|
|
46
|
-
|
|
47
|
-
const failedMatch = testLine.match(/(\d+)\s+failed/i);
|
|
48
|
-
if (failedMatch) failed = parseInt(failedMatch[1], 10);
|
|
49
|
-
|
|
50
|
-
const skippedMatch = testLine.match(/(\d+)\s+skipped/i);
|
|
51
|
-
if (skippedMatch) skipped = parseInt(skippedMatch[1], 10);
|
|
52
|
-
|
|
53
|
-
const todoMatch = testLine.match(/(\d+)\s+todo/i);
|
|
54
|
-
if (todoMatch) skipped += parseInt(todoMatch[1], 10);
|
|
55
|
-
|
|
56
|
-
let result: 'PASSED' | 'FAILED' = failed > 0 ? 'FAILED' : 'PASSED';
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
total,
|
|
60
|
-
passed,
|
|
61
|
-
failed,
|
|
62
|
-
skipped: skipped > 0 ? skipped : undefined,
|
|
63
|
-
result,
|
|
64
|
-
};
|
|
65
|
-
}
|
package/src/parsers/wjttc.ts
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WJTTC output parser
|
|
3
|
-
*
|
|
4
|
-
* Extracts test counts from the FAF / pc-ai WJTTC bar runner
|
|
5
|
-
* (tests/wjttc/run-wjttc.mjs and sibling suite runners).
|
|
6
|
-
*
|
|
7
|
-
* Two shapes:
|
|
8
|
-
*
|
|
9
|
-
* 1) Human log (default):
|
|
10
|
-
* ```
|
|
11
|
-
* pc-ai WJTTC
|
|
12
|
-
* ----------
|
|
13
|
-
* PASS VAL-01 valid-simple portfolio accepts
|
|
14
|
-
* FAIL OPT-01 …
|
|
15
|
-
* ----------
|
|
16
|
-
* 31/33 passed
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* 2) Machine JSON (`--json`):
|
|
20
|
-
* ```
|
|
21
|
-
* {
|
|
22
|
-
* "suite": "pc-ai-wjttc",
|
|
23
|
-
* "total": 33,
|
|
24
|
-
* "passed": 31,
|
|
25
|
-
* "failed": 2,
|
|
26
|
-
* "results": [ … ]
|
|
27
|
-
* }
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* Anchors are WJTTC-specific so this never false-matches Jest/Vitest/Bun.
|
|
31
|
-
* Prefer JSON when both appear in a teed stream (structured totals win).
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { TestResults } from './jest';
|
|
35
|
-
|
|
36
|
-
function stripAnsi(output: string): string {
|
|
37
|
-
// eslint-disable-next-line no-control-regex
|
|
38
|
-
return output
|
|
39
|
-
.replace(/\x1b\[[0-9;]*m/g, '')
|
|
40
|
-
.replace(/\r\n/g, '\n')
|
|
41
|
-
.replace(/\r/g, '\n');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Try to parse a WJTTC --json summary object from output.
|
|
46
|
-
* Handles pure JSON or JSON embedded after build noise in a tee file.
|
|
47
|
-
*/
|
|
48
|
-
function parseJsonSuite(clean: string): TestResults | null {
|
|
49
|
-
// Prefer a top-level object that names the suite.
|
|
50
|
-
const candidates: string[] = [];
|
|
51
|
-
|
|
52
|
-
const trimmed = clean.trim();
|
|
53
|
-
if (trimmed.startsWith('{')) {
|
|
54
|
-
candidates.push(trimmed);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Last {...} block often is the suite when logs precede JSON.
|
|
58
|
-
const lastBrace = clean.lastIndexOf('{');
|
|
59
|
-
if (lastBrace !== -1) {
|
|
60
|
-
candidates.push(clean.slice(lastBrace));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
for (const raw of candidates) {
|
|
64
|
-
try {
|
|
65
|
-
const body = JSON.parse(raw);
|
|
66
|
-
if (!body || typeof body !== 'object') continue;
|
|
67
|
-
if (body.suite !== 'pc-ai-wjttc') continue;
|
|
68
|
-
|
|
69
|
-
const total = Number(body.total);
|
|
70
|
-
const passed = Number(body.passed);
|
|
71
|
-
const failed = Number(body.failed);
|
|
72
|
-
if (!Number.isFinite(total) || total <= 0) continue;
|
|
73
|
-
if (!Number.isFinite(passed) || !Number.isFinite(failed)) continue;
|
|
74
|
-
|
|
75
|
-
// Prefer explicit fields; fall back to results[] length if needed.
|
|
76
|
-
let p = passed;
|
|
77
|
-
let f = failed;
|
|
78
|
-
let t = total;
|
|
79
|
-
if (Array.isArray(body.results) && body.results.length > 0) {
|
|
80
|
-
const fromRows = body.results.length;
|
|
81
|
-
if (t !== fromRows && fromRows > 0) {
|
|
82
|
-
// Trust results array as the executed set (partition honesty).
|
|
83
|
-
t = fromRows;
|
|
84
|
-
p = body.results.filter((r: { ok?: boolean }) => r && r.ok === true).length;
|
|
85
|
-
f = t - p;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
total: t,
|
|
91
|
-
passed: p,
|
|
92
|
-
failed: f,
|
|
93
|
-
result: f > 0 ? 'FAILED' : 'PASSED',
|
|
94
|
-
};
|
|
95
|
-
} catch {
|
|
96
|
-
// try next candidate
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Human WJTTC log: header + optional PASS/FAIL lines + "N/N passed".
|
|
105
|
-
*/
|
|
106
|
-
function parseHumanLog(clean: string): TestResults | null {
|
|
107
|
-
// Require the suite banner so we do not steal "12/12 passed" from other tools.
|
|
108
|
-
if (!/pc-ai\s+WJTTC/i.test(clean)) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const summary = clean.match(/(\d+)\s*\/\s*(\d+)\s+passed\b/i);
|
|
113
|
-
if (!summary) return null;
|
|
114
|
-
|
|
115
|
-
const passedFromSummary = parseInt(summary[1], 10);
|
|
116
|
-
const totalFromSummary = parseInt(summary[2], 10);
|
|
117
|
-
if (totalFromSummary <= 0) return null;
|
|
118
|
-
|
|
119
|
-
// Cross-check with PASS/FAIL lines when present (stronger than summary alone).
|
|
120
|
-
const passLines = clean.match(/^PASS\s+\S+/gm);
|
|
121
|
-
const failLines = clean.match(/^FAIL\s+\S+/gm);
|
|
122
|
-
const linePassed = passLines ? passLines.length : -1;
|
|
123
|
-
const lineFailed = failLines ? failLines.length : -1;
|
|
124
|
-
|
|
125
|
-
let total = totalFromSummary;
|
|
126
|
-
let passed = passedFromSummary;
|
|
127
|
-
let failed = total - passed;
|
|
128
|
-
|
|
129
|
-
if (linePassed >= 0 && lineFailed >= 0) {
|
|
130
|
-
const lineTotal = linePassed + lineFailed;
|
|
131
|
-
if (lineTotal === totalFromSummary) {
|
|
132
|
-
passed = linePassed;
|
|
133
|
-
failed = lineFailed;
|
|
134
|
-
total = lineTotal;
|
|
135
|
-
}
|
|
136
|
-
// If lines disagree with summary, still use summary (runner is source of truth)
|
|
137
|
-
// but failed must be non-negative.
|
|
138
|
-
if (failed < 0) failed = 0;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
total,
|
|
143
|
-
passed,
|
|
144
|
-
failed,
|
|
145
|
-
result: failed > 0 ? 'FAILED' : 'PASSED',
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Parse WJTTC (pc-ai bar) output to TestResults, or null if not WJTTC.
|
|
151
|
-
*/
|
|
152
|
-
export function parseWjttcOutput(output: string): TestResults | null {
|
|
153
|
-
const clean = stripAnsi(output);
|
|
154
|
-
|
|
155
|
-
// JSON first — structured totals when --json was used.
|
|
156
|
-
const fromJson = parseJsonSuite(clean);
|
|
157
|
-
if (fromJson) return fromJson;
|
|
158
|
-
|
|
159
|
-
return parseHumanLog(clean);
|
|
160
|
-
}
|
package/src/taf-core.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TAF Core - Simplified for GitHub Action
|
|
3
|
-
*
|
|
4
|
-
* Pure functions for reading, parsing, and updating .taf files
|
|
5
|
-
* Derived from faf-cli TAF implementation (MCP-portable)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from 'fs';
|
|
9
|
-
import * as yaml from 'yaml';
|
|
10
|
-
import { TestResults } from './parsers/jest';
|
|
11
|
-
|
|
12
|
-
interface TAFFile {
|
|
13
|
-
format_version?: string;
|
|
14
|
-
project?: string;
|
|
15
|
-
created?: string;
|
|
16
|
-
last_updated?: string;
|
|
17
|
-
test_history: TestRun[];
|
|
18
|
-
[key: string]: any; // Allow other fields
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface TestRun {
|
|
22
|
-
timestamp: string;
|
|
23
|
-
result: 'PASSED' | 'FAILED' | 'IMPROVED' | 'DEGRADED';
|
|
24
|
-
tests: {
|
|
25
|
-
total: number;
|
|
26
|
-
passed: number;
|
|
27
|
-
failed: number;
|
|
28
|
-
skipped?: number;
|
|
29
|
-
};
|
|
30
|
-
command?: string;
|
|
31
|
-
trigger?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Update .taf file with new test results
|
|
36
|
-
* Creates the file if it doesn't exist
|
|
37
|
-
*/
|
|
38
|
-
export function updateTafFile(
|
|
39
|
-
tafPath: string,
|
|
40
|
-
testResults: TestResults
|
|
41
|
-
): boolean {
|
|
42
|
-
try {
|
|
43
|
-
let taf: TAFFile;
|
|
44
|
-
|
|
45
|
-
// Check if .taf exists, create if not
|
|
46
|
-
if (fs.existsSync(tafPath)) {
|
|
47
|
-
const content = fs.readFileSync(tafPath, 'utf-8');
|
|
48
|
-
taf = parseTAF(content);
|
|
49
|
-
} else {
|
|
50
|
-
// Create new .taf file
|
|
51
|
-
const projectName = require('path').basename(require('path').dirname(tafPath));
|
|
52
|
-
taf = createTAF(projectName);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Create test run entry
|
|
56
|
-
const run: TestRun = {
|
|
57
|
-
timestamp: new Date().toISOString(),
|
|
58
|
-
result: testResults.result,
|
|
59
|
-
tests: {
|
|
60
|
-
total: testResults.total,
|
|
61
|
-
passed: testResults.passed,
|
|
62
|
-
failed: testResults.failed,
|
|
63
|
-
skipped: testResults.skipped,
|
|
64
|
-
},
|
|
65
|
-
trigger: 'github-actions',
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Append to history
|
|
69
|
-
taf.test_history.push(run);
|
|
70
|
-
|
|
71
|
-
// Serialize and write back
|
|
72
|
-
const updated = serializeTAF(taf);
|
|
73
|
-
fs.writeFileSync(tafPath, updated, 'utf-8');
|
|
74
|
-
|
|
75
|
-
return true;
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error('Failed to update .taf file:', error);
|
|
78
|
-
return false;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Create a new TAF file structure
|
|
84
|
-
*/
|
|
85
|
-
function createTAF(projectName: string): TAFFile {
|
|
86
|
-
return {
|
|
87
|
-
format_version: '1.0.0',
|
|
88
|
-
project: projectName,
|
|
89
|
-
created: new Date().toISOString(),
|
|
90
|
-
last_updated: new Date().toISOString(),
|
|
91
|
-
test_history: [],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Parse .taf file content
|
|
97
|
-
*/
|
|
98
|
-
function parseTAF(content: string): TAFFile {
|
|
99
|
-
// Split on document separator and take only first document
|
|
100
|
-
const lines = content.split('\n');
|
|
101
|
-
const yamlLines: string[] = [];
|
|
102
|
-
|
|
103
|
-
for (const line of lines) {
|
|
104
|
-
// Stop at document separator (but skip initial one if present)
|
|
105
|
-
if (line.trim() === '---' && yamlLines.length > 0) {
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
// Skip initial document separator
|
|
109
|
-
if (line.trim() === '---' && yamlLines.length === 0) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
yamlLines.push(line);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const parsed = yaml.parse(yamlLines.join('\n'));
|
|
116
|
-
|
|
117
|
-
// Basic validation
|
|
118
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
119
|
-
throw new Error('Invalid .taf file: not a valid YAML object');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!parsed.test_history || !Array.isArray(parsed.test_history)) {
|
|
123
|
-
throw new Error('Invalid .taf file: missing or invalid test_history');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return parsed as TAFFile;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Serialize TAF object back to YAML
|
|
131
|
-
* Uses yaml library to maintain existing format
|
|
132
|
-
*/
|
|
133
|
-
function serializeTAF(taf: TAFFile): string {
|
|
134
|
-
// Update last_updated timestamp
|
|
135
|
-
taf.last_updated = new Date().toISOString();
|
|
136
|
-
|
|
137
|
-
// Use yaml.stringify to preserve format
|
|
138
|
-
return yaml.stringify(taf);
|
|
139
|
-
}
|
package/test-mcp.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test action with claude-faf-mcp repository
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { parseJestOutput } from './src/parsers/jest';
|
|
8
|
-
import { updateTafFile } from './src/taf-core';
|
|
9
|
-
|
|
10
|
-
async function testMCP() {
|
|
11
|
-
console.log('Testing action with claude-faf-mcp repo...\n');
|
|
12
|
-
|
|
13
|
-
// Simulate Jest output (based on typical MCP test output)
|
|
14
|
-
const jestOutput = `
|
|
15
|
-
PASS tests/tools.test.ts
|
|
16
|
-
PASS tests/validation.test.ts
|
|
17
|
-
|
|
18
|
-
Test Suites: 2 passed, 2 total
|
|
19
|
-
Tests: 15 passed, 15 total
|
|
20
|
-
Snapshots: 0 total
|
|
21
|
-
Time: 1.234 s
|
|
22
|
-
`;
|
|
23
|
-
|
|
24
|
-
console.log('1. Parsing Jest output...');
|
|
25
|
-
const testResults = parseJestOutput(jestOutput);
|
|
26
|
-
|
|
27
|
-
if (!testResults) {
|
|
28
|
-
console.error('❌ Failed to parse Jest output');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log('✅ Parsed successfully:');
|
|
33
|
-
console.log(` - Total: ${testResults.total}`);
|
|
34
|
-
console.log(` - Passed: ${testResults.passed}`);
|
|
35
|
-
console.log(` - Failed: ${testResults.failed}`);
|
|
36
|
-
console.log(` - Result: ${testResults.result}\n`);
|
|
37
|
-
|
|
38
|
-
// Test with MCP .taf file
|
|
39
|
-
const tafPath = path.join('/Users/wolfejam/FAF/claude-faf-mcp', '.taf');
|
|
40
|
-
|
|
41
|
-
console.log('2. Checking .taf file...');
|
|
42
|
-
if (!fs.existsSync(tafPath)) {
|
|
43
|
-
console.error('❌ No .taf file found at:', tafPath);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
console.log('✅ Found .taf file\n');
|
|
47
|
-
|
|
48
|
-
console.log('3. Reading current .taf file...');
|
|
49
|
-
const beforeContent = fs.readFileSync(tafPath, 'utf-8');
|
|
50
|
-
const beforeLines = beforeContent.split('\n').filter(line => line.includes('timestamp')).length;
|
|
51
|
-
console.log(`✅ Current test runs: ${beforeLines}\n`);
|
|
52
|
-
|
|
53
|
-
console.log('4. Updating .taf file...');
|
|
54
|
-
const updated = await updateTafFile(tafPath, testResults);
|
|
55
|
-
|
|
56
|
-
if (!updated) {
|
|
57
|
-
console.error('❌ Failed to update .taf file');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
console.log('✅ .taf file updated\n');
|
|
61
|
-
|
|
62
|
-
console.log('5. Verifying update...');
|
|
63
|
-
const afterContent = fs.readFileSync(tafPath, 'utf-8');
|
|
64
|
-
const afterLines = afterContent.split('\n').filter(line => line.includes('timestamp')).length;
|
|
65
|
-
console.log(`✅ Test runs after update: ${afterLines}`);
|
|
66
|
-
|
|
67
|
-
if (afterLines !== beforeLines + 1) {
|
|
68
|
-
console.error('❌ Expected one additional run');
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log('\n✅ MCP test PASSED');
|
|
73
|
-
console.log('\nThe action successfully:');
|
|
74
|
-
console.log(' - Parsed Jest output (15/15 tests)');
|
|
75
|
-
console.log(' - Updated .taf file');
|
|
76
|
-
console.log(' - Added new test run entry');
|
|
77
|
-
console.log(' - Maintained proper format');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
testMCP().catch(error => {
|
|
81
|
-
console.error('❌ Test failed:', error);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
});
|