faf-taf-git 2.0.4 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.github/workflows/ci.yml +53 -0
  2. package/.github/workflows/claude-code-review.yml.disabled +29 -0
  3. package/.github/workflows/claude.yml +39 -0
  4. package/.github/workflows/homebrew.yml +19 -0
  5. package/.taf +79 -2
  6. package/CHANGELOG.md +28 -0
  7. package/CLAUDE.md +29 -21
  8. package/CODE_OF_CONDUCT.md +21 -0
  9. package/CONTRIBUTING.md +3 -0
  10. package/README.md +69 -12
  11. package/SECURITY.md +19 -0
  12. package/action.yml +6 -1
  13. package/badge.svg +21 -0
  14. package/dist/badge.d.ts +23 -0
  15. package/dist/badge.d.ts.map +1 -0
  16. package/dist/badge.js +147 -0
  17. package/dist/badge.js.map +1 -0
  18. package/dist/cli.d.ts +5 -6
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +144 -193
  21. package/dist/cli.js.map +1 -1
  22. package/dist/index.js +26045 -20566
  23. package/dist/index.js.map +1 -1
  24. package/dist/licenses.txt +0 -22
  25. package/dist/parsers/bun.d.ts +59 -0
  26. package/dist/parsers/bun.d.ts.map +1 -0
  27. package/dist/parsers/bun.js +100 -0
  28. package/dist/parsers/bun.js.map +1 -0
  29. package/dist/parsers/index.d.ts +20 -0
  30. package/dist/parsers/index.d.ts.map +1 -0
  31. package/dist/parsers/index.js +26 -0
  32. package/dist/parsers/index.js.map +1 -0
  33. package/dist/parsers/vitest.d.ts +17 -0
  34. package/dist/parsers/vitest.d.ts.map +1 -0
  35. package/dist/parsers/vitest.js +61 -0
  36. package/dist/parsers/vitest.js.map +1 -0
  37. package/package.json +6 -7
  38. package/project.faf +20 -24
  39. package/src/badge.ts +138 -0
  40. package/src/cli.ts +154 -200
  41. package/src/index.ts +79 -10
  42. package/src/parsers/bun.ts +103 -0
  43. package/src/parsers/index.ts +27 -0
  44. package/src/parsers/vitest.ts +65 -0
  45. package/tests/badge.test.ts +260 -0
  46. package/tests/bun-parser.test.ts +173 -0
  47. package/tests/vitest-parser.test.ts +112 -0
  48. package/.faf +0 -126
package/dist/licenses.txt CHANGED
@@ -59,28 +59,6 @@ The above copyright notice and this permission notice shall be included in all c
59
59
 
60
60
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
61
61
 
62
- @fastify/busboy
63
- MIT
64
- Copyright Brian White. All rights reserved.
65
-
66
- Permission is hereby granted, free of charge, to any person obtaining a copy
67
- of this software and associated documentation files (the "Software"), to
68
- deal in the Software without restriction, including without limitation the
69
- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
70
- sell copies of the Software, and to permit persons to whom the Software is
71
- furnished to do so, subject to the following conditions:
72
-
73
- The above copyright notice and this permission notice shall be included in
74
- all copies or substantial portions of the Software.
75
-
76
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
77
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
78
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
79
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
80
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
81
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
82
- IN THE SOFTWARE.
83
-
84
62
  tunnel
85
63
  MIT
86
64
  The MIT License (MIT)
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Bun test output parser
3
+ *
4
+ * Extracts test counts from `bun test` CLI output.
5
+ */
6
+ import { TestResults } from './jest';
7
+ /**
8
+ * Parse Bun test output to extract test results.
9
+ *
10
+ * Bun output format (summary block at the end of a run):
11
+ *
12
+ * ```
13
+ * 232 pass
14
+ * 1 skip
15
+ * 1 todo
16
+ * 0 fail
17
+ * 848 expect() calls
18
+ * Ran 234 tests across 9 files. [21.53s]
19
+ * ```
20
+ *
21
+ * Notes:
22
+ * - Anchored on `Ran <N> tests across <M> files.` — the canonical truthful
23
+ * total (includes skip + todo + pass + fail). Without this line, the parser
24
+ * returns null and the dispatcher falls through to jest/vitest.
25
+ * - Per-category counts (`pass`, `fail`, `skip`, `todo`) come from their own
26
+ * lines elsewhere in the same output. Singular forms — bun does not use
27
+ * `passed`/`failed`/`skipped` (intentional differentiator from jest/vitest).
28
+ * - `skipped` aggregates `skip` + `todo` (matches the convention vitest.ts
29
+ * already uses for parity in the shared TestResults shape).
30
+ */
31
+ export declare function parseBunOutput(output: string): TestResults | null;
32
+ /**
33
+ * Example Bun outputs for testing:
34
+ *
35
+ * All passing:
36
+ * ```
37
+ * 173 pass
38
+ * 0 fail
39
+ * Ran 173 tests across 8 files. [4.2s]
40
+ * ```
41
+ *
42
+ * Mixed (the grok-faf-mcp 1.4.9 shape):
43
+ * ```
44
+ * 232 pass
45
+ * 1 skip
46
+ * 1 todo
47
+ * 0 fail
48
+ * 848 expect() calls
49
+ * Ran 234 tests across 9 files. [21.53s]
50
+ * ```
51
+ *
52
+ * Failing:
53
+ * ```
54
+ * 170 pass
55
+ * 3 fail
56
+ * Ran 173 tests across 8 files. [5.1s]
57
+ * ```
58
+ */
59
+ //# sourceMappingURL=bun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/parsers/bun.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA0CjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ /**
3
+ * Bun test output parser
4
+ *
5
+ * Extracts test counts from `bun test` CLI output.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseBunOutput = parseBunOutput;
9
+ /**
10
+ * Parse Bun test output to extract test results.
11
+ *
12
+ * Bun output format (summary block at the end of a run):
13
+ *
14
+ * ```
15
+ * 232 pass
16
+ * 1 skip
17
+ * 1 todo
18
+ * 0 fail
19
+ * 848 expect() calls
20
+ * Ran 234 tests across 9 files. [21.53s]
21
+ * ```
22
+ *
23
+ * Notes:
24
+ * - Anchored on `Ran <N> tests across <M> files.` — the canonical truthful
25
+ * total (includes skip + todo + pass + fail). Without this line, the parser
26
+ * returns null and the dispatcher falls through to jest/vitest.
27
+ * - Per-category counts (`pass`, `fail`, `skip`, `todo`) come from their own
28
+ * lines elsewhere in the same output. Singular forms — bun does not use
29
+ * `passed`/`failed`/`skipped` (intentional differentiator from jest/vitest).
30
+ * - `skipped` aggregates `skip` + `todo` (matches the convention vitest.ts
31
+ * already uses for parity in the shared TestResults shape).
32
+ */
33
+ function parseBunOutput(output) {
34
+ // Strip ANSI color codes + normalize line endings (parity with siblings).
35
+ // eslint-disable-next-line no-control-regex
36
+ let cleanOutput = output.replace(/\x1b\[[0-9;]*m/g, '');
37
+ cleanOutput = cleanOutput.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
38
+ // Canonical anchor: bun-unique "Ran N tests across M files." summary line.
39
+ // Without this, this isn't bun output — let the dispatcher try the others.
40
+ const ranMatch = cleanOutput.match(/^Ran\s+(\d+)\s+tests?\s+across\s+\d+\s+files?/m);
41
+ if (!ranMatch)
42
+ return null;
43
+ const total = parseInt(ranMatch[1], 10);
44
+ if (total === 0)
45
+ return null;
46
+ let passed = 0;
47
+ let failed = 0;
48
+ let skipped = 0;
49
+ // Per-line counts. Each line is `<leading-ws><N> <singular-word>` on its own.
50
+ // Anchored with ^...$ + multiline to avoid catching the "expect() calls" line
51
+ // or any noise inside test descriptions.
52
+ const passedMatch = cleanOutput.match(/^\s*(\d+)\s+pass\s*$/m);
53
+ if (passedMatch)
54
+ passed = parseInt(passedMatch[1], 10);
55
+ const failedMatch = cleanOutput.match(/^\s*(\d+)\s+fail\s*$/m);
56
+ if (failedMatch)
57
+ failed = parseInt(failedMatch[1], 10);
58
+ const skipMatch = cleanOutput.match(/^\s*(\d+)\s+skip\s*$/m);
59
+ if (skipMatch)
60
+ skipped += parseInt(skipMatch[1], 10);
61
+ const todoMatch = cleanOutput.match(/^\s*(\d+)\s+todo\s*$/m);
62
+ if (todoMatch)
63
+ skipped += parseInt(todoMatch[1], 10);
64
+ const result = failed > 0 ? 'FAILED' : 'PASSED';
65
+ return {
66
+ total,
67
+ passed,
68
+ failed,
69
+ skipped: skipped > 0 ? skipped : undefined,
70
+ result,
71
+ };
72
+ }
73
+ /**
74
+ * Example Bun outputs for testing:
75
+ *
76
+ * All passing:
77
+ * ```
78
+ * 173 pass
79
+ * 0 fail
80
+ * Ran 173 tests across 8 files. [4.2s]
81
+ * ```
82
+ *
83
+ * Mixed (the grok-faf-mcp 1.4.9 shape):
84
+ * ```
85
+ * 232 pass
86
+ * 1 skip
87
+ * 1 todo
88
+ * 0 fail
89
+ * 848 expect() calls
90
+ * Ran 234 tests across 9 files. [21.53s]
91
+ * ```
92
+ *
93
+ * Failing:
94
+ * ```
95
+ * 170 pass
96
+ * 3 fail
97
+ * Ran 173 tests across 8 files. [5.1s]
98
+ * ```
99
+ */
100
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.js","sourceRoot":"","sources":["../../src/parsers/bun.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AA4BH,wCA0CC;AAlED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,cAAc,CAAC,MAAc;IAC3C,0EAA0E;IAC1E,4CAA4C;IAC5C,IAAI,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACxD,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEtE,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACrF,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,8EAA8E;IAC9E,8EAA8E;IAC9E,yCAAyC;IACzC,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC/D,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC/D,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC7D,IAAI,SAAS;QAAE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC7D,IAAI,SAAS;QAAE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,MAAM,MAAM,GAAwB,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAErE,OAAO;QACL,KAAK;QACL,MAAM;QACN,MAAM;QACN,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC1C,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Unified test output parser
3
+ *
4
+ * Tries each framework parser in order until one succeeds.
5
+ */
6
+ import { TestResults } from './jest';
7
+ export { TestResults } from './jest';
8
+ /**
9
+ * Parse test output from any supported framework.
10
+ *
11
+ * Order matters: Bun is tried first because its `Ran N tests across M files`
12
+ * anchor is bun-unique — it never false-matches on jest/vitest output. Putting
13
+ * it first also lets bun consumers ship truthful totals even when their CI
14
+ * still has a legacy `Tests: N passed, N total` jest-shim line appended to the
15
+ * test output (the bun parser ignores it and reads the canonical `Ran` line).
16
+ *
17
+ * Tries parsers in order: Bun, Jest, Vitest.
18
+ */
19
+ export declare function parseTestOutput(output: string): TestResults | null;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/parsers/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAKrC,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAElE"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * Unified test output parser
4
+ *
5
+ * Tries each framework parser in order until one succeeds.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseTestOutput = parseTestOutput;
9
+ const bun_1 = require("./bun");
10
+ const jest_1 = require("./jest");
11
+ const vitest_1 = require("./vitest");
12
+ /**
13
+ * Parse test output from any supported framework.
14
+ *
15
+ * Order matters: Bun is tried first because its `Ran N tests across M files`
16
+ * anchor is bun-unique — it never false-matches on jest/vitest output. Putting
17
+ * it first also lets bun consumers ship truthful totals even when their CI
18
+ * still has a legacy `Tests: N passed, N total` jest-shim line appended to the
19
+ * test output (the bun parser ignores it and reads the canonical `Ran` line).
20
+ *
21
+ * Tries parsers in order: Bun, Jest, Vitest.
22
+ */
23
+ function parseTestOutput(output) {
24
+ return (0, bun_1.parseBunOutput)(output) || (0, jest_1.parseJestOutput)(output) || (0, vitest_1.parseVitestOutput)(output);
25
+ }
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/parsers/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAoBH,0CAEC;AAnBD,+BAAuC;AACvC,iCAAyC;AACzC,qCAA6C;AAI7C;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,MAAc;IAC5C,OAAO,IAAA,oBAAc,EAAC,MAAM,CAAC,IAAI,IAAA,sBAAe,EAAC,MAAM,CAAC,IAAI,IAAA,0BAAiB,EAAC,MAAM,CAAC,CAAC;AACxF,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Vitest output parser
3
+ *
4
+ * Extracts test counts from Vitest CLI output
5
+ */
6
+ import { TestResults } from './jest';
7
+ /**
8
+ * Parse Vitest output to extract test results
9
+ *
10
+ * Vitest output formats:
11
+ * - " Tests 8 passed (8)"
12
+ * - " Tests 2 failed | 6 passed (8)"
13
+ * - " Tests 1 skipped | 7 passed (8)"
14
+ * - " Tests 1 failed | 2 skipped | 5 passed (8)"
15
+ */
16
+ export declare function parseVitestOutput(output: string): TestResults | null;
17
+ //# sourceMappingURL=vitest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["file:///Users/wolfejam/FAF/faf-taf-git/src/parsers/vitest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAErC;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA+CpE"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * Vitest output parser
4
+ *
5
+ * Extracts test counts from Vitest CLI output
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.parseVitestOutput = parseVitestOutput;
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
+ function parseVitestOutput(output) {
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
+ // Vitest summary pattern: " Tests ... (N)" where N is total in parens
24
+ const testSummaryPattern = /^\s*Tests\s+(.+?\(\d+\))/m;
25
+ const testLineMatch = cleanOutput.match(testSummaryPattern);
26
+ if (!testLineMatch || !testLineMatch[1]) {
27
+ return null;
28
+ }
29
+ const testLine = testLineMatch[1];
30
+ // Extract total from parentheses at end
31
+ const totalMatch = testLine.match(/\((\d+)\)\s*$/);
32
+ if (!totalMatch)
33
+ return null;
34
+ const total = parseInt(totalMatch[1], 10);
35
+ if (total === 0)
36
+ return null;
37
+ let passed = 0;
38
+ let failed = 0;
39
+ let skipped = 0;
40
+ const passedMatch = testLine.match(/(\d+)\s+passed/i);
41
+ if (passedMatch)
42
+ passed = parseInt(passedMatch[1], 10);
43
+ const failedMatch = testLine.match(/(\d+)\s+failed/i);
44
+ if (failedMatch)
45
+ failed = parseInt(failedMatch[1], 10);
46
+ const skippedMatch = testLine.match(/(\d+)\s+skipped/i);
47
+ if (skippedMatch)
48
+ skipped = parseInt(skippedMatch[1], 10);
49
+ const todoMatch = testLine.match(/(\d+)\s+todo/i);
50
+ if (todoMatch)
51
+ skipped += parseInt(todoMatch[1], 10);
52
+ let result = failed > 0 ? 'FAILED' : 'PASSED';
53
+ return {
54
+ total,
55
+ passed,
56
+ failed,
57
+ skipped: skipped > 0 ? skipped : undefined,
58
+ result,
59
+ };
60
+ }
61
+ //# sourceMappingURL=vitest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../../src/parsers/vitest.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAaH,8CA+CC;AAxDD;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAAC,MAAc;IAC9C,yBAAyB;IACzB,4CAA4C;IAC5C,IAAI,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC;IACxD,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEtE,uEAAuE;IACvE,MAAM,kBAAkB,GAAG,2BAA2B,CAAC;IACvD,MAAM,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAE5D,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAElC,wCAAwC;IACxC,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACnD,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE7B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACtD,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;IACtD,IAAI,WAAW;QAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACxD,IAAI,YAAY;QAAE,OAAO,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE1D,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAClD,IAAI,SAAS;QAAE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAErD,IAAI,MAAM,GAAwB,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAEnE,OAAO;QACL,KAAK;QACL,MAAM;QACN,MAAM;QACN,OAAO,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC1C,MAAM;KACP,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "faf-taf-git",
3
- "version": "2.0.4",
4
- "description": "Platform-agnostic TAF (Testing Activity Feed) updater - works in ANY CI/CD",
3
+ "version": "2.2.1",
4
+ "description": "A Test Receipt Printer for git. Every CI run prints a receipt to .taf — append-only, timestamped, cannot be gamed. Proof over time.",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
7
7
  "faf-taf-git": "dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsc",
11
- "test": "jest",
12
- "package": "ncc build src/index.ts -o dist --source-map --license licenses.txt"
10
+ "build": "tsc && ncc build src/index.ts -o dist --source-map --license licenses.txt",
11
+ "test": "jest"
13
12
  },
14
13
  "repository": {
15
14
  "type": "git",
@@ -31,9 +30,9 @@
31
30
  "author": "wolfejam",
32
31
  "license": "MIT",
33
32
  "dependencies": {
34
- "@actions/core": "^1.10.1",
33
+ "@actions/core": "^2.0.3",
35
34
  "@actions/exec": "^1.1.1",
36
- "@actions/github": "^6.0.0",
35
+ "@actions/github": "^6.0.1",
37
36
  "yaml": "^2.3.4"
38
37
  },
39
38
  "devDependencies": {
package/project.faf CHANGED
@@ -4,7 +4,7 @@ ai_score: 65%
4
4
  ai_confidence: MODERATE
5
5
  ai_value: 30_seconds_replaces_20_minutes_of_questions
6
6
  ai_tldr:
7
- project: faf-taf-git v2.0.4 - Git-tracked testing timelines for ANY CI/CD
7
+ project: faf-taf-git v2.1.1 - A Test Receipt Printer for git. Proof over time, cannot be gamed.
8
8
  stack: TypeScript CLI + GitHub Action (pre-capture architecture)
9
9
  quality_bar: ZERO_ERRORS_F1_STANDARDS
10
10
  status: PRODUCTION_READY (working in faf-cli repo)
@@ -12,15 +12,13 @@ ai_tldr:
12
12
  current_focus: Ecosystem adoption and user onboarding
13
13
  your_role: Support users, fix bugs, add test framework support
14
14
  instant_context:
15
- what_building: Platform-agnostic CLI + GitHub Action for TAF updates
15
+ what_building: A Test Receipt Printer for git. CLI runs on any CI/CD; prints append-only test receipts to .taf, kept in git. FAF-aware optionally — receipts can carry FAF score/identity, but TAF works on any repo.
16
16
  tech_stack: TypeScript
17
17
  main_language: TypeScript
18
18
  deployment: npm + GitHub Actions Marketplace
19
19
  architecture: v2.0.0 pre-capture pattern (separate test execution from receipt generation)
20
- use_cases: Standalone CLI, GitHub Action, GitLab CI, Jenkins, CircleCI, Local
21
- development
22
- test_support: Jest (fully supported) with plans for Mocha, Vitest, Pytest, Go
23
- test, Rust cargo
20
+ use_cases: Standalone CLI, GitHub Action, GitLab CI, Jenkins, CircleCI, Local development
21
+ test_support: Jest (fully supported) with plans for Mocha, Vitest, Pytest, Go test, Rust cargo
24
22
  golden_triangle: .faf (project DNA) + repo (code) + .taf (test proof)
25
23
  key_files:
26
24
  - src/index.ts (v2.0.0 rewrite - reads test output file)
@@ -40,19 +38,18 @@ context_quality:
40
38
  missing_context: []
41
39
  project:
42
40
  name: faf-taf-git
43
- goal: Platform-agnostic TAF (Testing Activity Feed) updater - works in ANY CI/CD
41
+ goal: Platform-agnostic CLI + GitHub Action for TAF updates
44
42
  innovation: On-the-fly Testing Audit Files - Unheard of in CI/CD
45
43
  taf_meaning:
46
44
  file: Testing Audit File (.taf)
47
45
  feed: Testing Activity Feed (live updates)
48
46
  main_language: TypeScript
49
- type: typescript
47
+ type: cli
50
48
  generated: 2025-10-28T01:26:05.656Z
51
49
  repository: https://github.com/Wolfe-Jam/faf-taf-git
52
50
  license: MIT
53
51
  author: wolfejam
54
- keywords: testing, taf, ci-cd, github-actions, gitlab-ci, platform-agnostic,
55
- jest, faf, git
52
+ keywords: testing, taf, ci-cd, github-actions, gitlab-ci, platform-agnostic, jest, faf, git
56
53
  mission: 🚀 Make Your AI Happy! 🧡 Trust-Driven 🤖
57
54
  revolution: 30 seconds replaces 20 minutes of questions
58
55
  brand: F1-Inspired Software Engineering - Championship AI Context
@@ -71,13 +68,14 @@ ai_instructions:
71
68
  - All TypeScript must pass strict mode
72
69
  - Test coverage required for new features
73
70
  stack:
74
- frontend: None
75
- css_framework: None
76
- ui_library: None
77
- state_management: None
78
- backend: None
71
+ frontend: slotignored
72
+ css_framework: slotignored
73
+ ui_library: slotignored
74
+ state_management: slotignored
75
+ backend: slotignored
79
76
  runtime: Node.js
80
- database: None
77
+ database: slotignored
78
+ connection: slotignored
81
79
  build: TypeScript Compiler
82
80
  package_manager: npm
83
81
  api_type: REST
@@ -130,8 +128,8 @@ taf_format:
130
128
  snapshots_faf_score: true
131
129
  closed_loop_system: .faf (DNA) + repo (code) + .taf (proof) = Golden Triangle
132
130
  deployment:
133
- npm_package: faf-taf-git@2.0.4
134
- github_action: Wolfe-Jam/faf-taf-git@v2.0.4
131
+ npm_package: faf-taf-git@2.1.1
132
+ github_action: Wolfe-Jam/faf-taf-git@v2.1.1
135
133
  marketplaces:
136
134
  - npm Registry
137
135
  - GitHub Actions Marketplace
@@ -184,7 +182,7 @@ resources:
184
182
  discussions: https://github.com/Wolfe-Jam/faf/discussions
185
183
  state:
186
184
  phase: production
187
- version: 2.0.4
185
+ version: 2.1.1
188
186
  focus: ecosystem_adoption
189
187
  status: production_ready
190
188
  achievements:
@@ -208,15 +206,13 @@ tags:
208
206
  - open-source
209
207
  user_defined: null
210
208
  human_context:
211
- who: Development teams
209
+ who: Development teams adopting test-receipt provenance — open-source maintainers proving honest test history, regulated industries (SOC 2 / ISO / HIPAA) needing append-only audit trails, supply-chain verifiers proving tests ran before artifacts shipped, AI-agent reviewers verifying agent code passes tests consistently, and FAF-ecosystem projects completing the CAR Framework (Claim → Audit → Receipt).
212
210
  what: TAF file updates in CI/CD pipelines
213
211
  why: Improve development efficiency
214
212
  where: Any CI/CD platform (GitHub, GitLab, Bitbucket)
215
213
  when: During CI/CD test runs
216
214
  how: Test-driven development
217
- additional_context: Part of Golden Triangle (.faf + repo + .taf). Tracks test
218
- runs permanently in git. Supports Jest, works as CLI or GitHub Action.
219
- Platform-agnostic core - runs on ANY CI/CD with Node.js.
215
+ additional_context: Part of Golden Triangle (.faf + repo + .taf). Tracks test runs permanently in git. Supports Jest, works as CLI or GitHub Action. Platform-agnostic core - runs on ANY CI/CD with Node.js.
220
216
  context_score: 0
221
217
  total_prd_score: 65
222
218
  success_rate: 50%
@@ -242,7 +238,7 @@ faf_dna:
242
238
  birth_date: 2025-10-28T01:26:05.692Z
243
239
  current_score: 50
244
240
  projectName: faf-taf-git
245
- projectGoal: Platform-agnostic TAF (Testing Activity Feed) updater - works in ANY CI/CD
241
+ projectGoal: A Test Receipt Printer for git — append-only test receipts on every CI run. Proof over time, cannot be gamed.
246
242
  generated: 2026-02-17T20:14:09.215Z
247
243
  last_major_update: v2.0.4 (2026-02-17)
248
244
  production_status: Working in faf-cli repository
package/src/badge.ts ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Badge Generator for faf-taf-git
3
+ *
4
+ * Generates shields.io-style SVG badges from .taf test history.
5
+ * Self-contained SVG with Verdana font, no external dependencies.
6
+ */
7
+
8
+ import * as fs from 'fs';
9
+ import * as yaml from 'yaml';
10
+
11
+ export interface BadgeOptions {
12
+ tafPath?: string;
13
+ label?: string;
14
+ }
15
+
16
+ export interface BadgeResult {
17
+ success: boolean;
18
+ svg?: string;
19
+ error?: string;
20
+ }
21
+
22
+ interface LatestRun {
23
+ passed: number;
24
+ total: number;
25
+ failed: number;
26
+ skipped: number;
27
+ }
28
+
29
+ const COLOR_PASS = '#003366'; // Navy blue (deep — white-text contrast; cyan was too light)
30
+ const COLOR_FAIL = '#E32400'; // Red
31
+ const COLOR_NONE = '#9F9F9F'; // Gray
32
+
33
+ /**
34
+ * Render a shields.io flat-style SVG badge.
35
+ */
36
+ export function renderBadgeSvg(label: string, value: string, color: string): string {
37
+ // Approximate text widths using Verdana 11px (6.5px per char average)
38
+ const charWidth = 6.5;
39
+ const padding = 10;
40
+ const labelWidth = Math.round(label.length * charWidth + padding * 2);
41
+ const valueWidth = Math.round(value.length * charWidth + padding * 2);
42
+ const totalWidth = labelWidth + valueWidth;
43
+ const height = 20;
44
+
45
+ const labelX = labelWidth / 2;
46
+ const valueX = labelWidth + valueWidth / 2;
47
+
48
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="${height}" role="img" aria-label="${label}: ${value}">
49
+ <title>${label}: ${value}</title>
50
+ <linearGradient id="s" x2="0" y2="100%">
51
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
52
+ <stop offset="1" stop-opacity=".1"/>
53
+ </linearGradient>
54
+ <clipPath id="r">
55
+ <rect width="${totalWidth}" height="${height}" rx="3" fill="#fff"/>
56
+ </clipPath>
57
+ <g clip-path="url(#r)">
58
+ <rect width="${labelWidth}" height="${height}" fill="#555"/>
59
+ <rect x="${labelWidth}" width="${valueWidth}" height="${height}" fill="${color}"/>
60
+ <rect width="${totalWidth}" height="${height}" fill="url(#s)"/>
61
+ </g>
62
+ <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
63
+ <text x="${labelX}" y="14" fill="#010101" fill-opacity=".3">${label}</text>
64
+ <text x="${labelX}" y="13" fill="#fff">${label}</text>
65
+ <text x="${valueX}" y="14" fill="#010101" fill-opacity=".3">${value}</text>
66
+ <text x="${valueX}" y="13" fill="#fff">${value}</text>
67
+ </g>
68
+ </svg>`;
69
+ }
70
+
71
+ /**
72
+ * Read a .taf file and extract the latest test run.
73
+ */
74
+ function getLatestRun(tafPath: string): LatestRun | null {
75
+ if (!fs.existsSync(tafPath)) {
76
+ return null;
77
+ }
78
+
79
+ const content = fs.readFileSync(tafPath, 'utf-8');
80
+
81
+ // Handle YAML document separators (same logic as taf-core.ts)
82
+ const lines = content.split('\n');
83
+ const yamlLines: string[] = [];
84
+ for (const line of lines) {
85
+ if (line.trim() === '---' && yamlLines.length > 0) break;
86
+ if (line.trim() === '---' && yamlLines.length === 0) continue;
87
+ yamlLines.push(line);
88
+ }
89
+
90
+ const parsed = yaml.parse(yamlLines.join('\n'));
91
+
92
+ if (!parsed || typeof parsed !== 'object') return null;
93
+ if (!Array.isArray(parsed.test_history) || parsed.test_history.length === 0) return null;
94
+
95
+ const latest = parsed.test_history[parsed.test_history.length - 1];
96
+ if (!latest.tests || typeof latest.tests.total !== 'number') return null;
97
+
98
+ return {
99
+ passed: latest.tests.passed ?? 0,
100
+ total: latest.tests.total,
101
+ failed: latest.tests.failed ?? 0,
102
+ skipped: latest.tests.skipped ?? 0,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Generate a badge SVG from a .taf file.
108
+ */
109
+ export function generateBadge(options: BadgeOptions = {}): BadgeResult {
110
+ const label = options.label || 'TAF';
111
+ const tafPath = options.tafPath || '.taf';
112
+
113
+ try {
114
+ const run = getLatestRun(tafPath);
115
+
116
+ if (!run) {
117
+ return {
118
+ success: true,
119
+ svg: renderBadgeSvg(label, 'no data', COLOR_NONE),
120
+ };
121
+ }
122
+
123
+ const skippedSuffix = run.skipped > 0 ? ` (${run.skipped} skipped)` : '';
124
+ const displayPassed = run.failed === 0 ? run.total : run.passed;
125
+ const value = `${displayPassed}/${run.total} passing${skippedSuffix}`;
126
+ const color = run.failed === 0 ? COLOR_PASS : COLOR_FAIL;
127
+
128
+ return {
129
+ success: true,
130
+ svg: renderBadgeSvg(label, value, color),
131
+ };
132
+ } catch (error) {
133
+ return {
134
+ success: false,
135
+ error: error instanceof Error ? error.message : 'Failed to generate badge',
136
+ };
137
+ }
138
+ }