@yasserkhanorg/impact-gate 2.0.0 → 2.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.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/commands/install_skill.d.ts +2 -0
  3. package/dist/cli/commands/install_skill.d.ts.map +1 -0
  4. package/dist/cli/commands/install_skill.js +60 -0
  5. package/dist/cli/parse_args.js +1 -1
  6. package/dist/cli/types.d.ts +1 -1
  7. package/dist/cli/types.d.ts.map +1 -1
  8. package/dist/cli/usage.d.ts.map +1 -1
  9. package/dist/cli/usage.js +1 -0
  10. package/dist/cli.js +7 -1
  11. package/dist/esm/cli/commands/install_skill.js +57 -0
  12. package/dist/esm/cli/parse_args.js +1 -1
  13. package/dist/esm/cli/usage.js +1 -0
  14. package/dist/esm/cli.js +7 -1
  15. package/dist/esm/qa-agent/cli.js +26 -0
  16. package/dist/esm/qa-agent/finding_taxonomy.js +102 -0
  17. package/dist/esm/qa-agent/health_score.js +99 -0
  18. package/dist/esm/qa-agent/orchestrator.js +67 -9
  19. package/dist/esm/qa-agent/phase2/agent_loop.js +13 -1
  20. package/dist/esm/qa-agent/phase2/tools.js +10 -4
  21. package/dist/esm/qa-agent/phase25/fix_loop.js +238 -0
  22. package/dist/esm/qa-agent/phase25/fix_tools.js +262 -0
  23. package/dist/esm/qa-agent/phase25/wtf_heuristic.js +60 -0
  24. package/dist/esm/qa-agent/phase3/reporter.js +100 -30
  25. package/dist/esm/qa-agent/phase3/verdict.js +21 -3
  26. package/dist/esm/qa-agent/regression/baseline.js +89 -0
  27. package/dist/qa-agent/cli.js +26 -0
  28. package/dist/qa-agent/finding_taxonomy.d.ts +23 -0
  29. package/dist/qa-agent/finding_taxonomy.d.ts.map +1 -0
  30. package/dist/qa-agent/finding_taxonomy.js +108 -0
  31. package/dist/qa-agent/health_score.d.ts +19 -0
  32. package/dist/qa-agent/health_score.d.ts.map +1 -0
  33. package/dist/qa-agent/health_score.js +104 -0
  34. package/dist/qa-agent/orchestrator.d.ts.map +1 -1
  35. package/dist/qa-agent/orchestrator.js +67 -9
  36. package/dist/qa-agent/phase2/agent_loop.d.ts.map +1 -1
  37. package/dist/qa-agent/phase2/agent_loop.js +13 -1
  38. package/dist/qa-agent/phase2/tools.d.ts.map +1 -1
  39. package/dist/qa-agent/phase2/tools.js +10 -4
  40. package/dist/qa-agent/phase25/fix_loop.d.ts +4 -0
  41. package/dist/qa-agent/phase25/fix_loop.d.ts.map +1 -0
  42. package/dist/qa-agent/phase25/fix_loop.js +244 -0
  43. package/dist/qa-agent/phase25/fix_tools.d.ts +18 -0
  44. package/dist/qa-agent/phase25/fix_tools.d.ts.map +1 -0
  45. package/dist/qa-agent/phase25/fix_tools.js +266 -0
  46. package/dist/qa-agent/phase25/wtf_heuristic.d.ts +27 -0
  47. package/dist/qa-agent/phase25/wtf_heuristic.d.ts.map +1 -0
  48. package/dist/qa-agent/phase25/wtf_heuristic.js +64 -0
  49. package/dist/qa-agent/phase3/reporter.d.ts +2 -2
  50. package/dist/qa-agent/phase3/reporter.d.ts.map +1 -1
  51. package/dist/qa-agent/phase3/reporter.js +100 -30
  52. package/dist/qa-agent/phase3/verdict.d.ts +2 -2
  53. package/dist/qa-agent/phase3/verdict.d.ts.map +1 -1
  54. package/dist/qa-agent/phase3/verdict.js +21 -3
  55. package/dist/qa-agent/regression/baseline.d.ts +14 -0
  56. package/dist/qa-agent/regression/baseline.d.ts.map +1 -0
  57. package/dist/qa-agent/regression/baseline.js +94 -0
  58. package/dist/qa-agent/types.d.ts +65 -2
  59. package/dist/qa-agent/types.d.ts.map +1 -1
  60. package/package.json +2 -1
  61. package/skills/qa/SKILL.md +138 -0
package/README.md CHANGED
@@ -63,7 +63,7 @@ The fastest way to evaluate the package is the deterministic CI path. These comm
63
63
  Install the package:
64
64
 
65
65
  ```bash
66
- npm install @yasserkhanorg/impact-gate
66
+ npm install -D @yasserkhanorg/impact-gate
67
67
  ```
68
68
 
69
69
  Requires Node.js >= 20. Ships both CommonJS and ESM builds.
@@ -0,0 +1,2 @@
1
+ export declare function runInstallSkillCommand(skillName?: string): void;
2
+ //# sourceMappingURL=install_skill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install_skill.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/install_skill.ts"],"names":[],"mappings":"AAsBA,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CA8C/D"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.runInstallSkillCommand = runInstallSkillCommand;
6
+ const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ // skills/ lives at the package root, two levels up from dist/cli/commands/
9
+ const SKILLS_SOURCE = (0, path_1.join)(__dirname, '..', '..', '..', 'skills');
10
+ function getSkillsDir() {
11
+ if ((0, fs_1.existsSync)(SKILLS_SOURCE)) {
12
+ return SKILLS_SOURCE;
13
+ }
14
+ throw new Error('Could not find skills/ directory in the impact-gate package. Reinstall with: npm install @yasserkhanorg/impact-gate');
15
+ }
16
+ function listAvailableSkills(skillsDir) {
17
+ return (0, fs_1.readdirSync)(skillsDir, { withFileTypes: true })
18
+ .filter((d) => d.isDirectory() && (0, fs_1.existsSync)((0, path_1.join)(skillsDir, d.name, 'SKILL.md')))
19
+ .map((d) => d.name);
20
+ }
21
+ function runInstallSkillCommand(skillName) {
22
+ const targetDir = process.cwd();
23
+ const claudeSkillsDir = (0, path_1.join)(targetDir, '.claude', 'skills');
24
+ const skillsDir = getSkillsDir();
25
+ const available = listAvailableSkills(skillsDir);
26
+ if (!skillName) {
27
+ // List available skills
28
+ console.log('');
29
+ console.log(' Available skills:');
30
+ console.log('');
31
+ for (const name of available) {
32
+ const installed = (0, fs_1.existsSync)((0, path_1.join)(claudeSkillsDir, name, 'SKILL.md'));
33
+ const status = installed ? ' (installed)' : '';
34
+ console.log(` /${name}${status}`);
35
+ }
36
+ console.log('');
37
+ console.log(' Usage: impact-gate install-skill <name>');
38
+ console.log(' Example: impact-gate install-skill qa');
39
+ console.log('');
40
+ return;
41
+ }
42
+ if (!available.includes(skillName)) {
43
+ console.error(`Unknown skill: "${skillName}". Available: ${available.join(', ')}`);
44
+ process.exit(1);
45
+ }
46
+ const source = (0, path_1.join)(skillsDir, skillName);
47
+ const dest = (0, path_1.join)(claudeSkillsDir, skillName);
48
+ if ((0, fs_1.existsSync)((0, path_1.join)(dest, 'SKILL.md'))) {
49
+ console.log(` /${skillName} is already installed at .claude/skills/${skillName}/`);
50
+ console.log(' To reinstall, remove the directory first and re-run.');
51
+ return;
52
+ }
53
+ (0, fs_1.mkdirSync)(dest, { recursive: true });
54
+ (0, fs_1.cpSync)(source, dest, { recursive: true });
55
+ console.log('');
56
+ console.log(` Installed /${skillName} → .claude/skills/${skillName}/`);
57
+ console.log('');
58
+ console.log(' You can now use /' + skillName + ' in Claude Code.');
59
+ console.log('');
60
+ }
@@ -168,7 +168,7 @@ const COMMANDS = new Set([
168
168
  'finalize-generated-tests', 'feedback',
169
169
  'traceability-capture', 'traceability-ingest',
170
170
  'analyze', 'llm-health', 'train', 'crew', 'cost-report', 'gate',
171
- 'bootstrap',
171
+ 'bootstrap', 'install-skill',
172
172
  ]);
173
173
  // ---------------------------------------------------------------------------
174
174
  // Parser
@@ -1,5 +1,5 @@
1
1
  import type { AnalysisProfile, FrameworkType } from '../agent/config.js';
2
- export type Command = 'init' | 'impact' | 'plan' | 'heal' | 'suggest' | 'generate' | 'finalize-generated-tests' | 'feedback' | 'traceability-capture' | 'traceability-ingest' | 'analyze' | 'llm-health' | 'train' | 'crew' | 'cost-report' | 'gate' | 'bootstrap';
2
+ export type Command = 'init' | 'impact' | 'plan' | 'heal' | 'suggest' | 'generate' | 'finalize-generated-tests' | 'feedback' | 'traceability-capture' | 'traceability-ingest' | 'analyze' | 'llm-health' | 'train' | 'crew' | 'cost-report' | 'gate' | 'bootstrap' | 'install-skill';
3
3
  export interface ParsedArgs {
4
4
  command?: Command;
5
5
  configPath?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,eAAe,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEvE,MAAM,MAAM,OAAO,GACf,MAAM,GACJ,QAAQ,GACR,MAAM,GACN,MAAM,GACN,SAAS,GACT,UAAU,GACV,0BAA0B,GAC1B,UAAU,GACV,sBAAsB,GACtB,qBAAqB,GACrB,SAAS,GACT,YAAY,GACZ,OAAO,GACP,MAAM,GACN,aAAa,GACb,MAAM,GACN,WAAW,CAAC;AAElB,MAAM,WAAW,UAAU;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,qBAAqB,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;IACtD,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,gBAAgB,GAAG,eAAe,CAAC,CAAC;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAGnB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,iBAAiB,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAG9B,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,eAAe,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEvE,MAAM,MAAM,OAAO,GACf,MAAM,GACJ,QAAQ,GACR,MAAM,GACN,MAAM,GACN,SAAS,GACT,UAAU,GACV,0BAA0B,GAC1B,UAAU,GACV,sBAAsB,GACtB,qBAAqB,GACrB,SAAS,GACT,YAAY,GACZ,OAAO,GACP,MAAM,GACN,aAAa,GACb,MAAM,GACN,WAAW,GACX,eAAe,CAAC;AAEtB,MAAM,WAAW,UAAU;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;IAC/D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,qBAAqB,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;IACtD,kBAAkB,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,gBAAgB,GAAG,eAAe,CAAC,CAAC;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAG3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAGnB,aAAa,CAAC,EAAE,MAAM,CAAC;IAGvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,iBAAiB,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAG9B,YAAY,CAAC,EAAE,OAAO,CAAC;IAGvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,IAAI,IAAI,CAyGjC"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,IAAI,IAAI,CA0GjC"}
package/dist/cli/usage.js CHANGED
@@ -24,6 +24,7 @@ function printUsage() {
24
24
  ' init [--yes]',
25
25
  ' train --path <project-root> [--no-enrich] [--validate] [--since <ref>] [--pr <num>]',
26
26
  ' bootstrap --path <app-root> [options]',
27
+ ' install-skill [name] Install a Claude Code skill (e.g. /qa)',
27
28
  ' feedback --path <app-root> --feedback-input <json>',
28
29
  ' traceability-capture --path <app-root> --traceability-report <json>',
29
30
  ' traceability-ingest --path <app-root> --traceability-input <json>',
package/dist/cli.js CHANGED
@@ -22,9 +22,10 @@ const crew_js_1 = require("./cli/commands/crew.js");
22
22
  const cost_report_js_1 = require("./cli/commands/cost_report.js");
23
23
  const gate_js_1 = require("./cli/commands/gate.js");
24
24
  const bootstrap_js_1 = require("./cli/commands/bootstrap.js");
25
+ const install_skill_js_1 = require("./cli/commands/install_skill.js");
25
26
  const errors_js_1 = require("./cli/errors.js");
26
27
  // Commands that skip default resolution (they handle their own setup)
27
- const SKIP_DEFAULTS_COMMANDS = new Set(['init', 'llm-health', 'cost-report', 'bootstrap']);
28
+ const SKIP_DEFAULTS_COMMANDS = new Set(['init', 'llm-health', 'cost-report', 'bootstrap', 'install-skill']);
28
29
  // Commands that need path/testsRoot/framework/since
29
30
  const NEEDS_DEFAULTS_COMMANDS = new Set([
30
31
  'impact', 'plan', 'suggest', 'crew', 'generate', 'heal', 'analyze', 'train',
@@ -51,6 +52,11 @@ async function main() {
51
52
  await (0, init_js_1.runInitCommand)(hasYes);
52
53
  return;
53
54
  }
55
+ if (args.command === 'install-skill') {
56
+ const skillName = process.argv[3]; // impact-gate install-skill <name>
57
+ (0, install_skill_js_1.runInstallSkillCommand)(skillName);
58
+ return;
59
+ }
54
60
  if (args.command === 'bootstrap') {
55
61
  await (0, bootstrap_js_1.runBootstrapCommand)(args);
56
62
  return;
@@ -0,0 +1,57 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ import { existsSync, mkdirSync, cpSync, readdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ // skills/ lives at the package root, two levels up from dist/cli/commands/
6
+ const SKILLS_SOURCE = join(__dirname, '..', '..', '..', 'skills');
7
+ function getSkillsDir() {
8
+ if (existsSync(SKILLS_SOURCE)) {
9
+ return SKILLS_SOURCE;
10
+ }
11
+ throw new Error('Could not find skills/ directory in the impact-gate package. Reinstall with: npm install @yasserkhanorg/impact-gate');
12
+ }
13
+ function listAvailableSkills(skillsDir) {
14
+ return readdirSync(skillsDir, { withFileTypes: true })
15
+ .filter((d) => d.isDirectory() && existsSync(join(skillsDir, d.name, 'SKILL.md')))
16
+ .map((d) => d.name);
17
+ }
18
+ export function runInstallSkillCommand(skillName) {
19
+ const targetDir = process.cwd();
20
+ const claudeSkillsDir = join(targetDir, '.claude', 'skills');
21
+ const skillsDir = getSkillsDir();
22
+ const available = listAvailableSkills(skillsDir);
23
+ if (!skillName) {
24
+ // List available skills
25
+ console.log('');
26
+ console.log(' Available skills:');
27
+ console.log('');
28
+ for (const name of available) {
29
+ const installed = existsSync(join(claudeSkillsDir, name, 'SKILL.md'));
30
+ const status = installed ? ' (installed)' : '';
31
+ console.log(` /${name}${status}`);
32
+ }
33
+ console.log('');
34
+ console.log(' Usage: impact-gate install-skill <name>');
35
+ console.log(' Example: impact-gate install-skill qa');
36
+ console.log('');
37
+ return;
38
+ }
39
+ if (!available.includes(skillName)) {
40
+ console.error(`Unknown skill: "${skillName}". Available: ${available.join(', ')}`);
41
+ process.exit(1);
42
+ }
43
+ const source = join(skillsDir, skillName);
44
+ const dest = join(claudeSkillsDir, skillName);
45
+ if (existsSync(join(dest, 'SKILL.md'))) {
46
+ console.log(` /${skillName} is already installed at .claude/skills/${skillName}/`);
47
+ console.log(' To reinstall, remove the directory first and re-run.');
48
+ return;
49
+ }
50
+ mkdirSync(dest, { recursive: true });
51
+ cpSync(source, dest, { recursive: true });
52
+ console.log('');
53
+ console.log(` Installed /${skillName} → .claude/skills/${skillName}/`);
54
+ console.log('');
55
+ console.log(' You can now use /' + skillName + ' in Claude Code.');
56
+ console.log('');
57
+ }
@@ -162,7 +162,7 @@ const COMMANDS = new Set([
162
162
  'finalize-generated-tests', 'feedback',
163
163
  'traceability-capture', 'traceability-ingest',
164
164
  'analyze', 'llm-health', 'train', 'crew', 'cost-report', 'gate',
165
- 'bootstrap',
165
+ 'bootstrap', 'install-skill',
166
166
  ]);
167
167
  // ---------------------------------------------------------------------------
168
168
  // Parser
@@ -21,6 +21,7 @@ export function printUsage() {
21
21
  ' init [--yes]',
22
22
  ' train --path <project-root> [--no-enrich] [--validate] [--since <ref>] [--pr <num>]',
23
23
  ' bootstrap --path <app-root> [options]',
24
+ ' install-skill [name] Install a Claude Code skill (e.g. /qa)',
24
25
  ' feedback --path <app-root> --feedback-input <json>',
25
26
  ' traceability-capture --path <app-root> --traceability-report <json>',
26
27
  ' traceability-ingest --path <app-root> --traceability-input <json>',
package/dist/esm/cli.js CHANGED
@@ -20,9 +20,10 @@ import { runCrewCommand } from './cli/commands/crew.js';
20
20
  import { runCostReportCommand } from './cli/commands/cost_report.js';
21
21
  import { runGateCommand } from './cli/commands/gate.js';
22
22
  import { runBootstrapCommand } from './cli/commands/bootstrap.js';
23
+ import { runInstallSkillCommand } from './cli/commands/install_skill.js';
23
24
  import { classifyError, EXIT_CODES } from './cli/errors.js';
24
25
  // Commands that skip default resolution (they handle their own setup)
25
- const SKIP_DEFAULTS_COMMANDS = new Set(['init', 'llm-health', 'cost-report', 'bootstrap']);
26
+ const SKIP_DEFAULTS_COMMANDS = new Set(['init', 'llm-health', 'cost-report', 'bootstrap', 'install-skill']);
26
27
  // Commands that need path/testsRoot/framework/since
27
28
  const NEEDS_DEFAULTS_COMMANDS = new Set([
28
29
  'impact', 'plan', 'suggest', 'crew', 'generate', 'heal', 'analyze', 'train',
@@ -49,6 +50,11 @@ async function main() {
49
50
  await runInitCommand(hasYes);
50
51
  return;
51
52
  }
53
+ if (args.command === 'install-skill') {
54
+ const skillName = process.argv[3]; // impact-gate install-skill <name>
55
+ runInstallSkillCommand(skillName);
56
+ return;
57
+ }
52
58
  if (args.command === 'bootstrap') {
53
59
  await runBootstrapCommand(args);
54
60
  return;
@@ -7,6 +7,7 @@ const MODES = new Set(['pr', 'hunt', 'fix', 'release']);
7
7
  const KNOWN_FLAGS = new Set([
8
8
  '--base-url', '--since', '--phase', '--time', '--budget',
9
9
  '--headed', '--tests-root', '--project', '--output', '--help', '-h',
10
+ '--fix-tier', '--no-fix', '--regression',
10
11
  ]);
11
12
  function printUsage() {
12
13
  console.log(`
@@ -28,6 +29,9 @@ Options:
28
29
  --tests-root <path> Path to tests directory
29
30
  --project <name> Playwright project name
30
31
  --output <dir> Output directory (default: .e2e-ai-agents)
32
+ --fix-tier <tier> Fix tier: quick (critical+high), standard (+medium), exhaustive (+low) (default: standard)
33
+ --no-fix Skip the fix loop (Phase 2.5)
34
+ --regression Compare against previous baseline
31
35
  --help Show this help
32
36
 
33
37
  Examples:
@@ -59,6 +63,9 @@ function parseCliArgs(argv) {
59
63
  let testsRoot;
60
64
  let project;
61
65
  let outputDir;
66
+ let fixTier = 'standard';
67
+ let fixEnabled = true;
68
+ let regression = false;
62
69
  // For hunt mode, the second positional arg is the target
63
70
  let startFlags = 1;
64
71
  if (mode === 'hunt' && argv[1] && !argv[1].startsWith('--')) {
@@ -122,6 +129,22 @@ function parseCliArgs(argv) {
122
129
  outputDir = next;
123
130
  i++;
124
131
  break;
132
+ case '--fix-tier': {
133
+ const validTiers = new Set(['quick', 'standard', 'exhaustive']);
134
+ if (!validTiers.has(next)) {
135
+ console.error(`Error: --fix-tier must be quick, standard, or exhaustive (got "${next}")`);
136
+ process.exit(1);
137
+ }
138
+ fixTier = next;
139
+ i++;
140
+ break;
141
+ }
142
+ case '--no-fix':
143
+ fixEnabled = false;
144
+ break;
145
+ case '--regression':
146
+ regression = true;
147
+ break;
125
148
  default:
126
149
  if (arg.startsWith('--') && !KNOWN_FLAGS.has(arg)) {
127
150
  console.error(`Warning: unknown flag "${arg}" (ignored)`);
@@ -178,6 +201,9 @@ function parseCliArgs(argv) {
178
201
  testsRoot,
179
202
  project,
180
203
  outputDir,
204
+ fixTier,
205
+ fixEnabled,
206
+ regression,
181
207
  };
182
208
  }
183
209
  async function main() {
@@ -0,0 +1,102 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ // ---------------------------------------------------------------------------
4
+ // Legacy type → canonical category mapping
5
+ // ---------------------------------------------------------------------------
6
+ const LEGACY_TO_CATEGORY = {
7
+ 'bug': 'functional',
8
+ 'gap': 'functional',
9
+ 'visual-regression': 'visual',
10
+ 'ux-issue': 'ux',
11
+ };
12
+ const CANONICAL_CATEGORIES = new Set([
13
+ 'visual', 'functional', 'ux', 'content', 'performance', 'console', 'accessibility',
14
+ ]);
15
+ /**
16
+ * Normalize any FindingType (legacy or canonical) to a canonical FindingCategory.
17
+ * Returns 'functional' for unrecognized types.
18
+ */
19
+ export function normalizeFindingType(raw) {
20
+ if (CANONICAL_CATEGORIES.has(raw)) {
21
+ return raw;
22
+ }
23
+ return LEGACY_TO_CATEGORY[raw] ?? 'functional';
24
+ }
25
+ /**
26
+ * Map a FindingType to its HealthScoreCategory.
27
+ * Same logic as normalizeFindingType — the category names match.
28
+ */
29
+ export function normalizeFindingCategory(type) {
30
+ if (type === 'verified-ok') {
31
+ return 'functional';
32
+ }
33
+ return normalizeFindingType(type);
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Severity definitions
37
+ // ---------------------------------------------------------------------------
38
+ export const SEVERITY_DEFINITIONS = {
39
+ critical: 'Blocks a core workflow, causes data loss, or crashes the app. Examples: form submit error page, checkout broken, data deleted without confirmation.',
40
+ high: 'Major feature broken or unusable, no workaround. Examples: search returns wrong results, file upload silently fails, auth redirect loop.',
41
+ medium: 'Feature works but with noticeable problems, workaround exists. Examples: slow page load (>5s), missing form validation, layout broken on mobile only.',
42
+ low: 'Minor cosmetic or polish issue. Examples: typo in footer, 1px alignment, hover state inconsistent.',
43
+ info: 'Observation or suggestion, not a defect. Examples: missing alt text noted, potential optimization.',
44
+ };
45
+ // ---------------------------------------------------------------------------
46
+ // Category definitions
47
+ // ---------------------------------------------------------------------------
48
+ export const CATEGORY_DEFINITIONS = {
49
+ visual: {
50
+ label: 'Visual/UI',
51
+ description: 'Layout breaks, broken images, z-index issues, font/color inconsistencies, animation glitches, alignment issues, dark mode problems.',
52
+ examples: ['Overlapping elements', 'Clipped text', 'Horizontal scrollbar', 'Incorrect z-index'],
53
+ },
54
+ functional: {
55
+ label: 'Functional',
56
+ description: 'Broken links, dead buttons, form validation failures, incorrect redirects, state not persisting, race conditions.',
57
+ examples: ['404 links', 'Click does nothing', 'Form bypasses validation', 'Data lost on refresh'],
58
+ },
59
+ ux: {
60
+ label: 'UX',
61
+ description: 'Confusing navigation, missing loading indicators, slow interactions, unclear error messages, no destructive-action confirmation.',
62
+ examples: ['No breadcrumbs', 'No loading spinner', '>500ms with no feedback', '"Something went wrong" with no detail'],
63
+ },
64
+ content: {
65
+ label: 'Content',
66
+ description: 'Typos, grammar errors, outdated text, placeholder/lorem ipsum left in, truncated text, wrong labels.',
67
+ examples: ['Typo in heading', 'Lorem ipsum visible', 'Button label says "Submit" instead of "Save"'],
68
+ },
69
+ performance: {
70
+ label: 'Performance',
71
+ description: 'Slow page loads (>3s), janky scrolling, layout shifts, excessive network requests, large unoptimized images.',
72
+ examples: ['Page takes 5s to load', 'Content jumping after load', '>50 network requests'],
73
+ },
74
+ console: {
75
+ label: 'Console/Errors',
76
+ description: 'JavaScript exceptions, failed network requests (4xx/5xx), deprecation warnings, CORS errors, mixed content.',
77
+ examples: ['Uncaught TypeError', '500 on API call', 'CORS blocked', 'Mixed content warning'],
78
+ },
79
+ accessibility: {
80
+ label: 'Accessibility',
81
+ description: 'Missing alt text, unlabeled inputs, broken keyboard navigation, focus traps, missing ARIA attributes, insufficient contrast.',
82
+ examples: ['Image without alt', 'Can\'t tab to button', 'Modal focus trap', 'Low contrast text'],
83
+ },
84
+ };
85
+ // ---------------------------------------------------------------------------
86
+ // Fix eligibility
87
+ // ---------------------------------------------------------------------------
88
+ const TIER_SEVERITIES = {
89
+ quick: new Set(['critical', 'high']),
90
+ standard: new Set(['critical', 'high', 'medium']),
91
+ exhaustive: new Set(['critical', 'high', 'medium', 'low']),
92
+ };
93
+ /**
94
+ * Determine whether a finding should be fixed based on the selected tier.
95
+ * 'info' severity and 'verified-ok' type are never fixable.
96
+ */
97
+ export function isFixable(finding, tier) {
98
+ if (finding.type === 'verified-ok' || finding.severity === 'info') {
99
+ return false;
100
+ }
101
+ return TIER_SEVERITIES[tier].has(finding.severity);
102
+ }
@@ -0,0 +1,99 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ import { normalizeFindingCategory } from './finding_taxonomy.js';
4
+ // ---------------------------------------------------------------------------
5
+ // Category weights (must sum to 1.0)
6
+ // ---------------------------------------------------------------------------
7
+ const CATEGORY_WEIGHTS = {
8
+ console: 0.15,
9
+ links: 0.10,
10
+ visual: 0.10,
11
+ functional: 0.20,
12
+ ux: 0.15,
13
+ performance: 0.10,
14
+ content: 0.05,
15
+ accessibility: 0.15,
16
+ };
17
+ // ---------------------------------------------------------------------------
18
+ // Severity deductions
19
+ // ---------------------------------------------------------------------------
20
+ const SEVERITY_DEDUCTIONS = {
21
+ critical: 25,
22
+ high: 15,
23
+ medium: 8,
24
+ low: 3,
25
+ info: 0,
26
+ };
27
+ // ---------------------------------------------------------------------------
28
+ // Public API
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Map a finding to its health-score category.
32
+ * Handles both canonical categories and legacy finding types.
33
+ */
34
+ export function mapFindingToCategory(finding) {
35
+ return normalizeFindingCategory(finding.type);
36
+ }
37
+ /**
38
+ * Compute a weighted health score (0-100) from a set of findings.
39
+ *
40
+ * Each of 8 categories starts at 100. Per finding, deduct based on severity
41
+ * (critical -25, high -15, medium -8, low -3). Clamp each category at 0.
42
+ * Overall = sum(category_score * weight).
43
+ */
44
+ export function computeHealthScore(findings) {
45
+ // Initialize per-category state
46
+ const categoryState = {
47
+ console: { score: 100, findings: [] },
48
+ links: { score: 100, findings: [] },
49
+ visual: { score: 100, findings: [] },
50
+ functional: { score: 100, findings: [] },
51
+ ux: { score: 100, findings: [] },
52
+ performance: { score: 100, findings: [] },
53
+ content: { score: 100, findings: [] },
54
+ accessibility: { score: 100, findings: [] },
55
+ };
56
+ // Apply deductions
57
+ for (const finding of findings) {
58
+ if (finding.type === 'verified-ok') {
59
+ continue;
60
+ }
61
+ const category = mapFindingToCategory(finding);
62
+ const deduction = SEVERITY_DEDUCTIONS[finding.severity] ?? 0;
63
+ categoryState[category].score = Math.max(0, categoryState[category].score - deduction);
64
+ categoryState[category].findings.push(finding.id);
65
+ }
66
+ // Build category scores
67
+ const categories = Object.keys(CATEGORY_WEIGHTS).map((cat) => ({
68
+ category: cat,
69
+ score: categoryState[cat].score,
70
+ weight: CATEGORY_WEIGHTS[cat],
71
+ findings: categoryState[cat].findings,
72
+ }));
73
+ // Compute weighted overall
74
+ const overall = Math.round(categories.reduce((sum, c) => sum + c.score * c.weight, 0));
75
+ return {
76
+ overall,
77
+ categories,
78
+ computedAt: new Date().toISOString(),
79
+ };
80
+ }
81
+ /**
82
+ * Render a health score as a markdown table.
83
+ */
84
+ export function formatHealthScoreMarkdown(score) {
85
+ const lines = [
86
+ `## Health Score: ${score.overall}/100`,
87
+ '',
88
+ '| Category | Score | Weight | Issues |',
89
+ '|----------|-------|--------|--------|',
90
+ ];
91
+ for (const cat of score.categories) {
92
+ const pct = `${Math.round(cat.weight * 100)}%`;
93
+ lines.push(`| ${capitalize(cat.category)} | ${cat.score} | ${pct} | ${cat.findings.length} |`);
94
+ }
95
+ return lines.join('\n');
96
+ }
97
+ function capitalize(s) {
98
+ return s.charAt(0).toUpperCase() + s.slice(1);
99
+ }
@@ -5,10 +5,14 @@ import { mkdirSync } from 'fs';
5
5
  import { logger } from '../logger.js';
6
6
  import { runPhase1 } from './phase1/runner.js';
7
7
  import { runAgentLoop } from './phase2/agent_loop.js';
8
+ import { AgentBrowser } from './phase2/agent_browser.js';
8
9
  import { computeVerdict } from './phase3/verdict.js';
9
10
  import { generateReport } from './phase3/reporter.js';
10
11
  import { generateSpecsForFindings } from './phase3/spec_generator.js';
11
12
  import { submitFeedback } from './phase3/feedback.js';
13
+ import { computeHealthScore } from './health_score.js';
14
+ import { runFixLoop } from './phase25/fix_loop.js';
15
+ import { saveBaseline, loadBaseline, compareBaselines } from './regression/baseline.js';
12
16
  function emptyPhase2Result() {
13
17
  return { findings: [], flowsExplored: [], actionsCount: 0, tokensUsed: 0, costUSD: 0, durationMs: 0 };
14
18
  }
@@ -76,15 +80,64 @@ export async function runQAAgent(inputConfig) {
76
80
  return earlyReturn(config, phase1, phase2);
77
81
  }
78
82
  // -----------------------------------------------------------------------
83
+ // Phase 2.5: Fix Loop (optional)
84
+ // -----------------------------------------------------------------------
85
+ let phase25;
86
+ let healthScore = phase2.healthScore || computeHealthScore(phase2.findings);
87
+ if (config.fixEnabled !== false && phase2.findings.length > 0) {
88
+ logger.info('=== Phase 2.5: Fix Loop ===');
89
+ // Create a browser instance for the fix loop to verify fixes
90
+ const fixBrowser = new AgentBrowser({ session: config.headed ? 'qa-fix-headed' : undefined });
91
+ try {
92
+ const projectRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf-8' }).trim();
93
+ phase25 = await runFixLoop(config, phase2.findings, fixBrowser, projectRoot);
94
+ healthScore = phase25.healthScoreAfter;
95
+ logger.info('Phase 2.5 complete', {
96
+ attempted: phase25.fixesAttempted,
97
+ verified: phase25.fixesVerified,
98
+ reverted: phase25.fixesReverted,
99
+ scoreDelta: `${phase25.healthScoreBefore.overall} → ${phase25.healthScoreAfter.overall}`,
100
+ });
101
+ }
102
+ catch (err) {
103
+ logger.warn('Phase 2.5 failed, continuing without fixes', { error: String(err) });
104
+ }
105
+ finally {
106
+ if (!config.headed) {
107
+ fixBrowser.close();
108
+ }
109
+ }
110
+ }
111
+ // -----------------------------------------------------------------------
112
+ // Regression comparison (optional)
113
+ // -----------------------------------------------------------------------
114
+ let regressionComparison;
115
+ if (config.regression) {
116
+ const baseline = loadBaseline(outputDir);
117
+ if (baseline) {
118
+ regressionComparison = compareBaselines(healthScore, phase2.findings, baseline);
119
+ logger.info('Regression comparison', {
120
+ scoreDelta: regressionComparison.scoreDelta,
121
+ fixedIssues: regressionComparison.fixedIssues.length,
122
+ newIssues: regressionComparison.newIssues.length,
123
+ });
124
+ }
125
+ else {
126
+ logger.info('No baseline found — saving current run as baseline');
127
+ }
128
+ }
129
+ // Always save baseline for future comparisons
130
+ saveBaseline(outputDir, healthScore, phase2.findings, config.baseUrl);
131
+ // -----------------------------------------------------------------------
79
132
  // Phase 3: Report + Spec Generation + Verdict
80
133
  // -----------------------------------------------------------------------
81
134
  logger.info('=== Phase 3: Report & Verdict ===');
82
135
  // Generate specs for discovered bugs
83
136
  const generatedSpecs = generateSpecsForFindings(phase2.findings, config);
84
- // Compute verdict
85
- const verdict = computeVerdict(phase1, phase2);
137
+ // Compute verdict (now with health score)
138
+ const verdict = computeVerdict(phase1, phase2, healthScore, phase25);
86
139
  // Generate report
87
- const phase3 = generateReport(config, phase1, phase2, verdict, generatedSpecs);
140
+ const phase3 = generateReport(config, phase1, phase2, verdict, generatedSpecs, phase25, healthScore, regressionComparison);
88
141
  // Submit feedback
89
142
  try {
90
143
  submitFeedback(config);
@@ -94,27 +147,32 @@ export async function runQAAgent(inputConfig) {
94
147
  }
95
148
  logger.info(`=== QA Agent Complete: ${verdict.decision.toUpperCase()} ===`);
96
149
  logger.info(verdict.reason);
97
- return buildQAReport(config, phase1, phase2, phase3, verdict);
150
+ return buildQAReport(config, phase1, phase2, phase3, verdict, phase25, healthScore, regressionComparison);
98
151
  }
99
152
  function earlyReturn(config, phase1, phase2) {
100
153
  const p2 = phase2 || emptyPhase2Result();
101
- const verdict = computeVerdict(phase1, p2);
102
- const phase3 = generateReport(config, phase1, p2, verdict, []);
103
- return buildQAReport(config, phase1, p2, phase3, verdict);
154
+ const healthScore = computeHealthScore(p2.findings);
155
+ const verdict = computeVerdict(phase1, p2, healthScore);
156
+ const phase3 = generateReport(config, phase1, p2, verdict, [], undefined, healthScore);
157
+ return buildQAReport(config, phase1, p2, phase3, verdict, undefined, healthScore);
104
158
  }
105
- function buildQAReport(config, phase1, phase2, phase3, verdict) {
159
+ function buildQAReport(config, phase1, phase2, phase3, verdict, phase25, healthScore, regressionComparison) {
106
160
  return {
107
- schemaVersion: '1.0.0',
161
+ schemaVersion: '1.1.0',
108
162
  generatedAt: new Date().toISOString(),
109
163
  mode: config.mode,
110
164
  config: {
111
165
  baseUrl: config.baseUrl,
112
166
  timeLimitMinutes: config.timeLimitMinutes,
113
167
  budgetUSD: config.budgetUSD,
168
+ fixTier: config.fixTier,
114
169
  },
115
170
  phase1,
116
171
  phase2,
172
+ phase25,
117
173
  phase3,
118
174
  verdict,
175
+ healthScore,
176
+ regressionComparison,
119
177
  };
120
178
  }