ghagga 3.0.0 → 3.2.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 (130) hide show
  1. package/dist/commands/login.test.js +0 -2
  2. package/dist/commands/login.test.js.map +1 -1
  3. package/dist/commands/memory/backfill.d.ts +13 -0
  4. package/dist/commands/memory/backfill.d.ts.map +1 -0
  5. package/dist/commands/memory/backfill.js +47 -0
  6. package/dist/commands/memory/backfill.js.map +1 -0
  7. package/dist/commands/memory/backfill.test.d.ts +11 -0
  8. package/dist/commands/memory/backfill.test.d.ts.map +1 -0
  9. package/dist/commands/memory/backfill.test.js +127 -0
  10. package/dist/commands/memory/backfill.test.js.map +1 -0
  11. package/dist/commands/memory/clear.test.js +9 -0
  12. package/dist/commands/memory/clear.test.js.map +1 -1
  13. package/dist/commands/memory/delete.test.js +9 -0
  14. package/dist/commands/memory/delete.test.js.map +1 -1
  15. package/dist/commands/memory/index.d.ts +3 -3
  16. package/dist/commands/memory/index.d.ts.map +1 -1
  17. package/dist/commands/memory/index.js +5 -3
  18. package/dist/commands/memory/index.js.map +1 -1
  19. package/dist/commands/memory/list.test.js +9 -0
  20. package/dist/commands/memory/list.test.js.map +1 -1
  21. package/dist/commands/memory/search.test.js +9 -0
  22. package/dist/commands/memory/search.test.js.map +1 -1
  23. package/dist/commands/memory/show.test.js +9 -0
  24. package/dist/commands/memory/show.test.js.map +1 -1
  25. package/dist/commands/memory/stats.test.js +9 -0
  26. package/dist/commands/memory/stats.test.js.map +1 -1
  27. package/dist/commands/memory/utils.d.ts.map +1 -1
  28. package/dist/commands/memory/utils.js +14 -1
  29. package/dist/commands/memory/utils.js.map +1 -1
  30. package/dist/commands/review-output.test.js +37 -0
  31. package/dist/commands/review-output.test.js.map +1 -1
  32. package/dist/commands/review.d.ts +12 -0
  33. package/dist/commands/review.d.ts.map +1 -1
  34. package/dist/commands/review.js +239 -5
  35. package/dist/commands/review.js.map +1 -1
  36. package/dist/commands/review.mr-exit.test.d.ts +13 -0
  37. package/dist/commands/review.mr-exit.test.d.ts.map +1 -0
  38. package/dist/commands/review.mr-exit.test.js +190 -0
  39. package/dist/commands/review.mr-exit.test.js.map +1 -0
  40. package/dist/commands/review.postback-disclosure.test.d.ts +22 -0
  41. package/dist/commands/review.postback-disclosure.test.d.ts.map +1 -0
  42. package/dist/commands/review.postback-disclosure.test.js +192 -0
  43. package/dist/commands/review.postback-disclosure.test.js.map +1 -0
  44. package/dist/commands/review.pr-exit.test.d.ts +13 -0
  45. package/dist/commands/review.pr-exit.test.d.ts.map +1 -0
  46. package/dist/commands/review.pr-exit.test.js +162 -0
  47. package/dist/commands/review.pr-exit.test.js.map +1 -0
  48. package/dist/commands/review.pr-jenkins.test.d.ts +16 -0
  49. package/dist/commands/review.pr-jenkins.test.d.ts.map +1 -0
  50. package/dist/commands/review.pr-jenkins.test.js +106 -0
  51. package/dist/commands/review.pr-jenkins.test.js.map +1 -0
  52. package/dist/commands/review.pr-nativeid-fallback.test.d.ts +15 -0
  53. package/dist/commands/review.pr-nativeid-fallback.test.d.ts.map +1 -0
  54. package/dist/commands/review.pr-nativeid-fallback.test.js +151 -0
  55. package/dist/commands/review.pr-nativeid-fallback.test.js.map +1 -0
  56. package/dist/commands/review.test.js +13 -0
  57. package/dist/commands/review.test.js.map +1 -1
  58. package/dist/index.js +39 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/lib/cli-github-client-port.d.ts +52 -0
  61. package/dist/lib/cli-github-client-port.d.ts.map +1 -0
  62. package/dist/lib/cli-github-client-port.js +199 -0
  63. package/dist/lib/cli-github-client-port.js.map +1 -0
  64. package/dist/lib/cli-github-client-port.test.d.ts +10 -0
  65. package/dist/lib/cli-github-client-port.test.d.ts.map +1 -0
  66. package/dist/lib/cli-github-client-port.test.js +47 -0
  67. package/dist/lib/cli-github-client-port.test.js.map +1 -0
  68. package/dist/lib/cli-gitlab-client-port.d.ts +62 -0
  69. package/dist/lib/cli-gitlab-client-port.d.ts.map +1 -0
  70. package/dist/lib/cli-gitlab-client-port.js +209 -0
  71. package/dist/lib/cli-gitlab-client-port.js.map +1 -0
  72. package/dist/lib/cli-gitlab-client-port.test.d.ts +9 -0
  73. package/dist/lib/cli-gitlab-client-port.test.d.ts.map +1 -0
  74. package/dist/lib/cli-gitlab-client-port.test.js +292 -0
  75. package/dist/lib/cli-gitlab-client-port.test.js.map +1 -0
  76. package/dist/lib/config.d.ts +23 -0
  77. package/dist/lib/config.d.ts.map +1 -1
  78. package/dist/lib/config.js +9 -0
  79. package/dist/lib/config.js.map +1 -1
  80. package/dist/lib/embedding.d.ts +23 -0
  81. package/dist/lib/embedding.d.ts.map +1 -0
  82. package/dist/lib/embedding.js +39 -0
  83. package/dist/lib/embedding.js.map +1 -0
  84. package/dist/lib/embedding.test.d.ts +8 -0
  85. package/dist/lib/embedding.test.d.ts.map +1 -0
  86. package/dist/lib/embedding.test.js +101 -0
  87. package/dist/lib/embedding.test.js.map +1 -0
  88. package/dist/lib/forge-postback.d.ts +72 -0
  89. package/dist/lib/forge-postback.d.ts.map +1 -0
  90. package/dist/lib/forge-postback.js +39 -0
  91. package/dist/lib/forge-postback.js.map +1 -0
  92. package/dist/lib/gitlab-api.d.ts +50 -0
  93. package/dist/lib/gitlab-api.d.ts.map +1 -0
  94. package/dist/lib/gitlab-api.js +75 -0
  95. package/dist/lib/gitlab-api.js.map +1 -0
  96. package/dist/lib/gitlab-api.test.d.ts +2 -0
  97. package/dist/lib/gitlab-api.test.d.ts.map +1 -0
  98. package/dist/lib/gitlab-api.test.js +79 -0
  99. package/dist/lib/gitlab-api.test.js.map +1 -0
  100. package/dist/lib/gitlab-e2e.manual.test.d.ts +57 -0
  101. package/dist/lib/gitlab-e2e.manual.test.d.ts.map +1 -0
  102. package/dist/lib/gitlab-e2e.manual.test.js +135 -0
  103. package/dist/lib/gitlab-e2e.manual.test.js.map +1 -0
  104. package/dist/lib/gitlab-token.d.ts +17 -0
  105. package/dist/lib/gitlab-token.d.ts.map +1 -0
  106. package/dist/lib/gitlab-token.js +24 -0
  107. package/dist/lib/gitlab-token.js.map +1 -0
  108. package/dist/lib/gitlab-token.test.d.ts +2 -0
  109. package/dist/lib/gitlab-token.test.d.ts.map +1 -0
  110. package/dist/lib/gitlab-token.test.js +38 -0
  111. package/dist/lib/gitlab-token.test.js.map +1 -0
  112. package/dist/lib/oauth.test.js +0 -1
  113. package/dist/lib/oauth.test.js.map +1 -1
  114. package/dist/lib/pr-postback.d.ts +42 -0
  115. package/dist/lib/pr-postback.d.ts.map +1 -0
  116. package/dist/lib/pr-postback.js +44 -0
  117. package/dist/lib/pr-postback.js.map +1 -0
  118. package/dist/lib/pr-postback.test.d.ts +9 -0
  119. package/dist/lib/pr-postback.test.d.ts.map +1 -0
  120. package/dist/lib/pr-postback.test.js +212 -0
  121. package/dist/lib/pr-postback.test.js.map +1 -0
  122. package/dist/lib/pr-token.d.ts +15 -0
  123. package/dist/lib/pr-token.d.ts.map +1 -0
  124. package/dist/lib/pr-token.js +22 -0
  125. package/dist/lib/pr-token.js.map +1 -0
  126. package/dist/lib/pr-token.test.d.ts +2 -0
  127. package/dist/lib/pr-token.test.d.ts.map +1 -0
  128. package/dist/lib/pr-token.test.js +25 -0
  129. package/dist/lib/pr-token.test.js.map +1 -0
  130. package/package.json +9 -8
@@ -0,0 +1,190 @@
1
+ /**
2
+ * P4 — `ghagga review --mr N` (GitLab MR) post-back routes through the SHARED
3
+ * composition helper (composeForgePostback → postSummaryComment) and honors the
4
+ * SAME blocking-by-default exit semantics as `--pr` (FIX 3). `--pr-soft-fail`
5
+ * (shared) opts out.
6
+ *
7
+ * We mock the seams reviewCommand composes (pipeline, git, gitlab-token,
8
+ * gitlab-api, gitlab client port, post-back, core formatters) so these tests are
9
+ * deterministic + offline, and assert the resolved process exit code + that the
10
+ * GitLab adapter is the one constructed (shared helper routes --mr to GitLab).
11
+ */
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13
+ vi.mock('ghagga-core', () => ({
14
+ reviewPipeline: vi.fn(),
15
+ buildSarif: vi.fn().mockReturnValue({ version: '2.1.0', runs: [] }),
16
+ formatReviewComment: vi.fn().mockReturnValue('<!-- ghagga-review -->\nbody'),
17
+ formatBoxSummary: vi.fn().mockReturnValue([]),
18
+ REVIEW_COMMENT_MARKER: '<!-- ghagga-review -->',
19
+ DEFAULT_SETTINGS: {
20
+ enableSemgrep: true,
21
+ enableTrivy: true,
22
+ enableCpd: true,
23
+ enableMemory: true,
24
+ customRules: [],
25
+ ignorePatterns: [],
26
+ reviewLevel: 'normal',
27
+ enabledTools: [],
28
+ disabledTools: [],
29
+ },
30
+ DEFAULT_MODELS: { gateway: 'auto', 'cli-bridge': 'auto', ollama: 'llama3' },
31
+ initializeDefaultTools: vi.fn(),
32
+ toolRegistry: { getAll: vi.fn().mockReturnValue([]) },
33
+ EngramMemoryStorage: { create: vi.fn() },
34
+ SqliteMemoryStorage: { create: vi.fn() },
35
+ formatReviewResult: vi.fn(),
36
+ }));
37
+ vi.mock('node:child_process', () => ({ execSync: vi.fn() }));
38
+ vi.mock('node:fs', () => ({ readFileSync: vi.fn(), existsSync: vi.fn().mockReturnValue(false) }));
39
+ vi.mock('../lib/gitlab-token.js', () => ({ resolveMrToken: vi.fn() }));
40
+ vi.mock('../lib/pr-postback.js', () => ({ postSummaryComment: vi.fn() }));
41
+ vi.mock('../lib/gitlab-api.js', () => ({
42
+ parseGitLabRemote: vi.fn().mockReturnValue({ host: 'gitlab.com', projectPath: 'acme/widgets' }),
43
+ GitLabApiError: class extends Error {
44
+ },
45
+ }));
46
+ vi.mock('../lib/cli-gitlab-client-port.js', () => ({
47
+ createCliGitLabClientPort: vi.fn(),
48
+ resolveGitLabApiBase: vi.fn().mockReturnValue('https://gitlab.com/api/v4'),
49
+ resolveGitLabProjectId: vi.fn().mockResolvedValue('12345'),
50
+ }));
51
+ // Track which adapter the shared helper constructs (routing proof).
52
+ const gitlabCtor = vi.fn();
53
+ const githubCtor = vi.fn();
54
+ vi.mock('ghagga-forge', () => ({
55
+ GitHubForgeAdapter: class {
56
+ constructor(deps) {
57
+ githubCtor(deps);
58
+ }
59
+ },
60
+ GitLabForgeAdapter: class {
61
+ constructor(deps) {
62
+ gitlabCtor(deps);
63
+ }
64
+ },
65
+ StaticTokenProvider: class {
66
+ async getToken() {
67
+ return 'glpat';
68
+ }
69
+ },
70
+ }));
71
+ vi.mock('../lib/cli-github-client-port.js', () => ({ createCliGitHubClientPort: vi.fn() }));
72
+ import { execSync } from 'node:child_process';
73
+ import { reviewPipeline } from 'ghagga-core';
74
+ import { resolveGitLabProjectId } from '../lib/cli-gitlab-client-port.js';
75
+ import { resolveMrToken } from '../lib/gitlab-token.js';
76
+ import { postSummaryComment } from '../lib/pr-postback.js';
77
+ const mockExecSync = vi.mocked(execSync);
78
+ const mockReviewPipeline = vi.mocked(reviewPipeline);
79
+ const mockResolveMrToken = vi.mocked(resolveMrToken);
80
+ const mockPostSummaryComment = vi.mocked(postSummaryComment);
81
+ const mockResolveProjectId = vi.mocked(resolveGitLabProjectId);
82
+ function defaultOptions(overrides = {}) {
83
+ return {
84
+ mode: 'simple',
85
+ provider: 'gateway',
86
+ model: 'm',
87
+ apiKey: 'k',
88
+ semgrep: true,
89
+ trivy: true,
90
+ cpd: true,
91
+ memory: false,
92
+ verbose: false,
93
+ disableTools: [],
94
+ enableTools: [],
95
+ ...overrides,
96
+ };
97
+ }
98
+ function passedResult() {
99
+ return {
100
+ status: 'PASSED',
101
+ summary: 'ok',
102
+ findings: [],
103
+ staticAnalysis: {
104
+ semgrep: { status: 'skipped', findings: [], executionTimeMs: 0 },
105
+ trivy: { status: 'skipped', findings: [], executionTimeMs: 0 },
106
+ cpd: { status: 'skipped', findings: [], executionTimeMs: 0 },
107
+ },
108
+ memoryContext: null,
109
+ metadata: {
110
+ mode: 'simple',
111
+ provider: 'gateway',
112
+ model: 'm',
113
+ tokensUsed: 1,
114
+ executionTimeMs: 1,
115
+ toolsRun: [],
116
+ toolsSkipped: [],
117
+ fileList: [],
118
+ },
119
+ };
120
+ }
121
+ describe('reviewCommand — --mr post-back (P4, shared composition helper)', () => {
122
+ // biome-ignore lint/suspicious/noExplicitAny: spy
123
+ let exitSpy;
124
+ // biome-ignore lint/suspicious/noExplicitAny: spy
125
+ let errorSpy;
126
+ beforeEach(() => {
127
+ vi.clearAllMocks();
128
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
129
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
130
+ vi.spyOn(console, 'log').mockImplementation(() => { });
131
+ mockExecSync.mockReturnValue('git@gitlab.com:acme/widgets.git');
132
+ mockReviewPipeline.mockResolvedValue(passedResult());
133
+ });
134
+ afterEach(() => {
135
+ vi.restoreAllMocks();
136
+ });
137
+ it('--mr success → posts via the GitLab adapter through the SHARED helper, exit 0', async () => {
138
+ mockResolveMrToken.mockReturnValue('glpat');
139
+ mockPostSummaryComment.mockResolvedValue({ createdNativeId: 77, deletedNativeIds: [] });
140
+ const { reviewCommand } = await import('./review.js');
141
+ await reviewCommand('.', defaultOptions({ mr: 7, outputFormat: 'sarif' }));
142
+ // Routing proof: GitLab adapter constructed, NOT GitHub; ref via numeric id.
143
+ expect(gitlabCtor).toHaveBeenCalledOnce();
144
+ expect(githubCtor).not.toHaveBeenCalled();
145
+ expect(gitlabCtor).toHaveBeenCalledWith(expect.objectContaining({ projectId: '12345' }));
146
+ expect(mockResolveProjectId).toHaveBeenCalledWith('acme/widgets', 'glpat', 'https://gitlab.com/api/v4');
147
+ // Post routed through the shared forge-neutral postSummaryComment.
148
+ expect(mockPostSummaryComment).toHaveBeenCalledOnce();
149
+ expect(exitSpy).toHaveBeenCalledWith(0);
150
+ });
151
+ it('--mr + post-back throws → exit NON-ZERO (1) even when review PASSED', async () => {
152
+ mockResolveMrToken.mockReturnValue('glpat');
153
+ mockPostSummaryComment.mockRejectedValue(new Error('GitLab 500'));
154
+ const { reviewCommand } = await import('./review.js');
155
+ await reviewCommand('.', defaultOptions({ mr: 7, outputFormat: 'sarif' }));
156
+ expect(exitSpy).toHaveBeenCalledWith(1);
157
+ });
158
+ it('--mr + missing token → exit NON-ZERO (1) with a GitLab-specific message', async () => {
159
+ mockResolveMrToken.mockReturnValue(null);
160
+ const { reviewCommand } = await import('./review.js');
161
+ await reviewCommand('.', defaultOptions({ mr: 7, outputFormat: 'sarif' }));
162
+ expect(exitSpy).toHaveBeenCalledWith(1);
163
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('--mr requires a GitLab token'));
164
+ });
165
+ it('--mr + --pr-soft-fail + throws → WARNS and exits 0', async () => {
166
+ mockResolveMrToken.mockReturnValue('glpat');
167
+ mockPostSummaryComment.mockRejectedValue(new Error('GitLab 500'));
168
+ const { reviewCommand } = await import('./review.js');
169
+ await reviewCommand('.', defaultOptions({ mr: 7, prSoftFail: true, outputFormat: 'sarif' }));
170
+ expect(exitSpy).toHaveBeenCalledWith(0);
171
+ });
172
+ it('both --pr AND --mr → defensive guard exits 1 BEFORE the pipeline runs (FIX D)', async () => {
173
+ mockResolveMrToken.mockReturnValue('glpat');
174
+ const { reviewCommand } = await import('./review.js');
175
+ await reviewCommand('.', defaultOptions({ pr: 3, mr: 7, outputFormat: 'sarif' }));
176
+ // The guard fires FIRST: the first recorded exit is the guard's non-zero exit
177
+ // (in production process.exit terminates here; the test mock lets it fall
178
+ // through, so we assert on the FIRST exit call specifically).
179
+ expect(exitSpy.mock.calls[0]?.[0]).toBe(1);
180
+ });
181
+ it('does NOT mask a review-found-issues exit: --mr success + FAILED review → exit 1', async () => {
182
+ mockReviewPipeline.mockResolvedValue({ ...passedResult(), status: 'FAILED' });
183
+ mockResolveMrToken.mockReturnValue('glpat');
184
+ mockPostSummaryComment.mockResolvedValue({ createdNativeId: 77, deletedNativeIds: [] });
185
+ const { reviewCommand } = await import('./review.js');
186
+ await reviewCommand('.', defaultOptions({ mr: 7, outputFormat: 'sarif' }));
187
+ expect(exitSpy).toHaveBeenCalledWith(1);
188
+ });
189
+ });
190
+ //# sourceMappingURL=review.mr-exit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.mr-exit.test.js","sourceRoot":"","sources":["../../src/commands/review.mr-exit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * SECURITY HYGIENE — before the CLI posts a review back to a forge (`--pr` GitHub
3
+ * PR / `--mr` GitLab MR), it discloses the DESTINATION HOST on stderr so the user
4
+ * sees where their token is going BEFORE the network POST fires.
5
+ *
6
+ * Rationale: the GitLab API base is derived from the git remote host, so a
7
+ * poisoned `origin` could exfiltrate GITLAB_TOKEN. Disclosing the REAL resolved
8
+ * host makes that visible.
9
+ *
10
+ * These tests assert:
11
+ * - the disclosure line is emitted to STDERR (console.error), NOT stdout
12
+ * (stdout is reserved for SARIF / JSON / markdown output)
13
+ * - the host is correct for --pr (api.github.com) and --mr (gitlab.com)
14
+ * - a self-hosted GitLab remote discloses THAT host (real resolution, not a
15
+ * mock) — this is the poisoned-origin-visible case
16
+ *
17
+ * For the GitLab path we deliberately use the REAL parseGitLabRemote +
18
+ * resolveGitLabApiBase (only the network seams are mocked) so the host shown is
19
+ * the genuinely-resolved destination.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=review.postback-disclosure.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.postback-disclosure.test.d.ts","sourceRoot":"","sources":["../../src/commands/review.postback-disclosure.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * SECURITY HYGIENE — before the CLI posts a review back to a forge (`--pr` GitHub
3
+ * PR / `--mr` GitLab MR), it discloses the DESTINATION HOST on stderr so the user
4
+ * sees where their token is going BEFORE the network POST fires.
5
+ *
6
+ * Rationale: the GitLab API base is derived from the git remote host, so a
7
+ * poisoned `origin` could exfiltrate GITLAB_TOKEN. Disclosing the REAL resolved
8
+ * host makes that visible.
9
+ *
10
+ * These tests assert:
11
+ * - the disclosure line is emitted to STDERR (console.error), NOT stdout
12
+ * (stdout is reserved for SARIF / JSON / markdown output)
13
+ * - the host is correct for --pr (api.github.com) and --mr (gitlab.com)
14
+ * - a self-hosted GitLab remote discloses THAT host (real resolution, not a
15
+ * mock) — this is the poisoned-origin-visible case
16
+ *
17
+ * For the GitLab path we deliberately use the REAL parseGitLabRemote +
18
+ * resolveGitLabApiBase (only the network seams are mocked) so the host shown is
19
+ * the genuinely-resolved destination.
20
+ */
21
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22
+ vi.mock('ghagga-core', () => ({
23
+ reviewPipeline: vi.fn(),
24
+ buildSarif: vi.fn().mockReturnValue({ version: '2.1.0', runs: [] }),
25
+ formatReviewComment: vi.fn().mockReturnValue('<!-- ghagga-review -->\nbody'),
26
+ formatBoxSummary: vi.fn().mockReturnValue([]),
27
+ REVIEW_COMMENT_MARKER: '<!-- ghagga-review -->',
28
+ DEFAULT_SETTINGS: {
29
+ enableSemgrep: true,
30
+ enableTrivy: true,
31
+ enableCpd: true,
32
+ enableMemory: true,
33
+ customRules: [],
34
+ ignorePatterns: [],
35
+ reviewLevel: 'normal',
36
+ enabledTools: [],
37
+ disabledTools: [],
38
+ },
39
+ DEFAULT_MODELS: { gateway: 'auto', 'cli-bridge': 'auto', ollama: 'llama3' },
40
+ initializeDefaultTools: vi.fn(),
41
+ toolRegistry: { getAll: vi.fn().mockReturnValue([]) },
42
+ EngramMemoryStorage: { create: vi.fn() },
43
+ SqliteMemoryStorage: { create: vi.fn() },
44
+ formatReviewResult: vi.fn(),
45
+ }));
46
+ vi.mock('node:child_process', () => ({ execSync: vi.fn() }));
47
+ vi.mock('node:fs', () => ({ readFileSync: vi.fn(), existsSync: vi.fn().mockReturnValue(false) }));
48
+ // Token + post-back seams (network) are mocked; remote-parse + api-base are REAL.
49
+ vi.mock('../lib/pr-token.js', () => ({ resolvePrToken: vi.fn() }));
50
+ vi.mock('../lib/gitlab-token.js', () => ({ resolveMrToken: vi.fn() }));
51
+ vi.mock('../lib/pr-postback.js', () => ({ postSummaryComment: vi.fn() }));
52
+ vi.mock('../lib/github-api.js', () => ({
53
+ parseGitHubRemote: vi.fn().mockReturnValue({ owner: 'acme', repo: 'ci' }),
54
+ createComment: vi.fn(),
55
+ createIssue: vi.fn(),
56
+ ensureLabel: vi.fn(),
57
+ formatIssueBody: vi.fn(),
58
+ }));
59
+ // NOTE: gitlab-api.js (parseGitLabRemote) and the resolveGitLabApiBase host
60
+ // derivation are NOT mocked — we want the REAL resolved host disclosed.
61
+ vi.mock('../lib/cli-gitlab-client-port.js', async (importOriginal) => {
62
+ const actual = await importOriginal();
63
+ return {
64
+ ...actual,
65
+ createCliGitLabClientPort: vi.fn(),
66
+ resolveGitLabProjectId: vi.fn().mockResolvedValue('12345'),
67
+ };
68
+ });
69
+ vi.mock('ghagga-forge', () => ({
70
+ GitHubForgeAdapter: class {
71
+ },
72
+ GitLabForgeAdapter: class {
73
+ },
74
+ StaticTokenProvider: class {
75
+ async getToken() {
76
+ return 'tok';
77
+ }
78
+ },
79
+ }));
80
+ vi.mock('../lib/cli-github-client-port.js', () => ({ createCliGitHubClientPort: vi.fn() }));
81
+ import { execSync } from 'node:child_process';
82
+ import { reviewPipeline } from 'ghagga-core';
83
+ import { resolveMrToken } from '../lib/gitlab-token.js';
84
+ import { postSummaryComment } from '../lib/pr-postback.js';
85
+ import { resolvePrToken } from '../lib/pr-token.js';
86
+ const mockExecSync = vi.mocked(execSync);
87
+ const mockReviewPipeline = vi.mocked(reviewPipeline);
88
+ const mockResolvePrToken = vi.mocked(resolvePrToken);
89
+ const mockResolveMrToken = vi.mocked(resolveMrToken);
90
+ const mockPostSummaryComment = vi.mocked(postSummaryComment);
91
+ function defaultOptions(overrides = {}) {
92
+ return {
93
+ mode: 'simple',
94
+ provider: 'gateway',
95
+ model: 'm',
96
+ apiKey: 'k',
97
+ semgrep: true,
98
+ trivy: true,
99
+ cpd: true,
100
+ memory: false,
101
+ verbose: false,
102
+ disableTools: [],
103
+ enableTools: [],
104
+ ...overrides,
105
+ };
106
+ }
107
+ function passedResult() {
108
+ return {
109
+ status: 'PASSED',
110
+ summary: 'ok',
111
+ findings: [],
112
+ staticAnalysis: {
113
+ semgrep: { status: 'skipped', findings: [], executionTimeMs: 0 },
114
+ trivy: { status: 'skipped', findings: [], executionTimeMs: 0 },
115
+ cpd: { status: 'skipped', findings: [], executionTimeMs: 0 },
116
+ },
117
+ memoryContext: null,
118
+ metadata: {
119
+ mode: 'simple',
120
+ provider: 'gateway',
121
+ model: 'm',
122
+ tokensUsed: 1,
123
+ executionTimeMs: 1,
124
+ toolsRun: [],
125
+ toolsSkipped: [],
126
+ fileList: [],
127
+ },
128
+ };
129
+ }
130
+ describe('reviewCommand — forge post-back DESTINATION HOST disclosure (stderr)', () => {
131
+ // biome-ignore lint/suspicious/noExplicitAny: spy
132
+ let errorSpy;
133
+ // biome-ignore lint/suspicious/noExplicitAny: spy
134
+ let logSpy;
135
+ beforeEach(() => {
136
+ vi.clearAllMocks();
137
+ vi.spyOn(process, 'exit').mockImplementation((() => { }));
138
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
139
+ logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
140
+ mockReviewPipeline.mockResolvedValue(passedResult());
141
+ mockPostSummaryComment.mockResolvedValue({ createdNativeId: 77, deletedNativeIds: [] });
142
+ });
143
+ afterEach(() => {
144
+ vi.restoreAllMocks();
145
+ });
146
+ /** All console.error lines as one blob for substring assertions. */
147
+ function stderrBlob() {
148
+ return errorSpy.mock.calls.map((c) => String(c[0])).join('\n');
149
+ }
150
+ function stdoutBlob() {
151
+ return logSpy.mock.calls.map((c) => String(c[0])).join('\n');
152
+ }
153
+ it('--pr discloses api.github.com on stderr (not stdout), before the POST', async () => {
154
+ mockResolvePrToken.mockReturnValue('ghp');
155
+ mockExecSync.mockReturnValue('git@github.com:acme/ci.git');
156
+ const { reviewCommand } = await import('./review.js');
157
+ await reviewCommand('.', defaultOptions({ pr: 3, outputFormat: 'sarif' }));
158
+ expect(stderrBlob()).toContain('→ Posting PR #3 review to api.github.com');
159
+ // MUST NOT pollute stdout (SARIF lives there).
160
+ expect(stdoutBlob()).not.toContain('Posting PR');
161
+ expect(stdoutBlob()).not.toContain('api.github.com');
162
+ });
163
+ it('--mr discloses the gitlab.com host on stderr (not stdout)', async () => {
164
+ mockResolveMrToken.mockReturnValue('glpat');
165
+ mockExecSync.mockReturnValue('git@gitlab.com:acme/widgets.git');
166
+ const { reviewCommand } = await import('./review.js');
167
+ await reviewCommand('.', defaultOptions({ mr: 7, outputFormat: 'sarif' }));
168
+ expect(stderrBlob()).toContain('→ Posting MR #7 review to gitlab.com');
169
+ expect(stdoutBlob()).not.toContain('Posting MR');
170
+ expect(stdoutBlob()).not.toContain('gitlab.com');
171
+ });
172
+ it('--mr on a SELF-HOSTED GitLab discloses THAT host (poisoned-origin visible)', async () => {
173
+ mockResolveMrToken.mockReturnValue('glpat');
174
+ // The git remote points at a self-managed/poisoned host — the disclosure must
175
+ // surface the REAL resolved API host, not a hardcoded gitlab.com.
176
+ mockExecSync.mockReturnValue('git@gitlab.internal.example:team/app.git');
177
+ const { reviewCommand } = await import('./review.js');
178
+ await reviewCommand('.', defaultOptions({ mr: 42, outputFormat: 'sarif' }));
179
+ expect(stderrBlob()).toContain('→ Posting MR #42 review to gitlab.internal.example');
180
+ expect(stdoutBlob()).not.toContain('gitlab.internal.example');
181
+ });
182
+ it('the disclosure is emitted BEFORE the post (host shown even when the post throws)', async () => {
183
+ mockResolveMrToken.mockReturnValue('glpat');
184
+ mockExecSync.mockReturnValue('git@gitlab.com:acme/widgets.git');
185
+ mockPostSummaryComment.mockRejectedValue(new Error('GitLab 500'));
186
+ const { reviewCommand } = await import('./review.js');
187
+ await reviewCommand('.', defaultOptions({ mr: 7, outputFormat: 'sarif' }));
188
+ // Disclosure present even though the post failed afterwards.
189
+ expect(stderrBlob()).toContain('→ Posting MR #7 review to gitlab.com');
190
+ });
191
+ });
192
+ //# sourceMappingURL=review.postback-disclosure.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.postback-disclosure.test.js","sourceRoot":"","sources":["../../src/commands/review.postback-disclosure.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * FIX 3 (P3 4vr) — `--pr` post-back failure must be BLOCKING by default.
3
+ *
4
+ * `--pr` is an explicitly-requested side-effect. In CI (Jenkins) a post-back
5
+ * that throws — or a missing token — must FAIL the job (non-zero exit), not
6
+ * silently exit 0. `--pr-soft-fail` restores the old non-blocking behavior.
7
+ *
8
+ * We mock the seams reviewCommand composes (pipeline, git, token, post-back,
9
+ * core formatters) so these tests are deterministic and offline, and assert the
10
+ * resolved process exit code.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=review.pr-exit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.pr-exit.test.d.ts","sourceRoot":"","sources":["../../src/commands/review.pr-exit.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * FIX 3 (P3 4vr) — `--pr` post-back failure must be BLOCKING by default.
3
+ *
4
+ * `--pr` is an explicitly-requested side-effect. In CI (Jenkins) a post-back
5
+ * that throws — or a missing token — must FAIL the job (non-zero exit), not
6
+ * silently exit 0. `--pr-soft-fail` restores the old non-blocking behavior.
7
+ *
8
+ * We mock the seams reviewCommand composes (pipeline, git, token, post-back,
9
+ * core formatters) so these tests are deterministic and offline, and assert the
10
+ * resolved process exit code.
11
+ */
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13
+ // ─── Mock ghagga-core: only what review.ts touches on the --pr path ──
14
+ vi.mock('ghagga-core', () => ({
15
+ reviewPipeline: vi.fn(),
16
+ buildSarif: vi.fn().mockReturnValue({ version: '2.1.0', runs: [] }),
17
+ formatReviewComment: vi.fn().mockReturnValue('<!-- ghagga-review -->\nbody'),
18
+ formatBoxSummary: vi.fn().mockReturnValue([]),
19
+ REVIEW_COMMENT_MARKER: '<!-- ghagga-review -->',
20
+ DEFAULT_SETTINGS: {
21
+ enableSemgrep: true,
22
+ enableTrivy: true,
23
+ enableCpd: true,
24
+ enableMemory: true,
25
+ customRules: [],
26
+ ignorePatterns: [],
27
+ reviewLevel: 'normal',
28
+ enabledTools: [],
29
+ disabledTools: [],
30
+ },
31
+ DEFAULT_MODELS: { gateway: 'auto', 'cli-bridge': 'auto', ollama: 'llama3' },
32
+ initializeDefaultTools: vi.fn(),
33
+ toolRegistry: { getAll: vi.fn().mockReturnValue([]) },
34
+ EngramMemoryStorage: { create: vi.fn() },
35
+ SqliteMemoryStorage: { create: vi.fn() },
36
+ formatReviewResult: vi.fn(),
37
+ }));
38
+ vi.mock('node:child_process', () => ({ execSync: vi.fn() }));
39
+ vi.mock('node:fs', () => ({ readFileSync: vi.fn(), existsSync: vi.fn().mockReturnValue(false) }));
40
+ // PR-token + remote-parse + post-back seams.
41
+ vi.mock('../lib/pr-token.js', () => ({ resolvePrToken: vi.fn() }));
42
+ vi.mock('../lib/pr-postback.js', () => ({ postSummaryComment: vi.fn() }));
43
+ vi.mock('../lib/github-api.js', () => ({
44
+ parseGitHubRemote: vi.fn().mockReturnValue({ owner: 'acme', repo: 'ci' }),
45
+ createComment: vi.fn(),
46
+ createIssue: vi.fn(),
47
+ ensureLabel: vi.fn(),
48
+ formatIssueBody: vi.fn(),
49
+ }));
50
+ // The forge adapter + client port are constructed but never exercised (post-back
51
+ // is mocked); stub them so construction is cheap.
52
+ vi.mock('ghagga-forge', () => ({
53
+ GitHubForgeAdapter: class {
54
+ },
55
+ StaticTokenProvider: class {
56
+ async getToken() {
57
+ return 'tok';
58
+ }
59
+ },
60
+ }));
61
+ vi.mock('../lib/cli-github-client-port.js', () => ({ createCliGitHubClientPort: vi.fn() }));
62
+ import { execSync } from 'node:child_process';
63
+ import { reviewPipeline } from 'ghagga-core';
64
+ import { postSummaryComment } from '../lib/pr-postback.js';
65
+ import { resolvePrToken } from '../lib/pr-token.js';
66
+ const mockExecSync = vi.mocked(execSync);
67
+ const mockReviewPipeline = vi.mocked(reviewPipeline);
68
+ const mockResolvePrToken = vi.mocked(resolvePrToken);
69
+ const mockPostSummaryComment = vi.mocked(postSummaryComment);
70
+ function defaultOptions(overrides = {}) {
71
+ return {
72
+ mode: 'simple',
73
+ provider: 'gateway',
74
+ model: 'm',
75
+ apiKey: 'k',
76
+ semgrep: true,
77
+ trivy: true,
78
+ cpd: true,
79
+ memory: false,
80
+ verbose: false,
81
+ disableTools: [],
82
+ enableTools: [],
83
+ ...overrides,
84
+ };
85
+ }
86
+ function passedResult() {
87
+ return {
88
+ status: 'PASSED',
89
+ summary: 'ok',
90
+ findings: [],
91
+ staticAnalysis: {
92
+ semgrep: { status: 'skipped', findings: [], executionTimeMs: 0 },
93
+ trivy: { status: 'skipped', findings: [], executionTimeMs: 0 },
94
+ cpd: { status: 'skipped', findings: [], executionTimeMs: 0 },
95
+ },
96
+ memoryContext: null,
97
+ metadata: {
98
+ mode: 'simple',
99
+ provider: 'gateway',
100
+ model: 'm',
101
+ tokensUsed: 1,
102
+ executionTimeMs: 1,
103
+ toolsRun: [],
104
+ toolsSkipped: [],
105
+ fileList: [],
106
+ },
107
+ };
108
+ }
109
+ describe('reviewCommand — --pr post-back exit semantics (FIX 3)', () => {
110
+ // biome-ignore lint/suspicious/noExplicitAny: spy
111
+ let exitSpy;
112
+ // biome-ignore lint/suspicious/noExplicitAny: spy
113
+ let errorSpy;
114
+ beforeEach(() => {
115
+ vi.clearAllMocks();
116
+ exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
117
+ errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
118
+ vi.spyOn(console, 'log').mockImplementation(() => { });
119
+ mockExecSync.mockReturnValue('diff content');
120
+ mockReviewPipeline.mockResolvedValue(passedResult());
121
+ });
122
+ afterEach(() => {
123
+ vi.restoreAllMocks();
124
+ });
125
+ it('--pr + post-back throws → exits NON-ZERO (1) even when review PASSED', async () => {
126
+ mockResolvePrToken.mockReturnValue('gh-token');
127
+ mockPostSummaryComment.mockRejectedValue(new Error('GitHub 500'));
128
+ const { reviewCommand } = await import('./review.js');
129
+ await reviewCommand('.', defaultOptions({ pr: 42, outputFormat: 'sarif' }));
130
+ expect(exitSpy).toHaveBeenCalledWith(1);
131
+ });
132
+ it('--pr + missing token → exits NON-ZERO (1) with a clear message', async () => {
133
+ mockResolvePrToken.mockReturnValue(undefined);
134
+ const { reviewCommand } = await import('./review.js');
135
+ await reviewCommand('.', defaultOptions({ pr: 42, outputFormat: 'sarif' }));
136
+ expect(exitSpy).toHaveBeenCalledWith(1);
137
+ expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('--pr requires a GitHub token'));
138
+ });
139
+ it('--pr-soft-fail + post-back throws → WARNS and exits 0 (review PASSED)', async () => {
140
+ mockResolvePrToken.mockReturnValue('gh-token');
141
+ mockPostSummaryComment.mockRejectedValue(new Error('GitHub 500'));
142
+ const { reviewCommand } = await import('./review.js');
143
+ await reviewCommand('.', defaultOptions({ pr: 42, prSoftFail: true, outputFormat: 'sarif' }));
144
+ expect(exitSpy).toHaveBeenCalledWith(0);
145
+ });
146
+ it('--pr success → exits 0 when review PASSED', async () => {
147
+ mockResolvePrToken.mockReturnValue('gh-token');
148
+ mockPostSummaryComment.mockResolvedValue({ createdNativeId: 7, deletedNativeIds: [] });
149
+ const { reviewCommand } = await import('./review.js');
150
+ await reviewCommand('.', defaultOptions({ pr: 42, outputFormat: 'sarif' }));
151
+ expect(exitSpy).toHaveBeenCalledWith(0);
152
+ });
153
+ it('does NOT mask a review-found-issues exit: --pr success + FAILED review → exit 1', async () => {
154
+ mockReviewPipeline.mockResolvedValue({ ...passedResult(), status: 'FAILED' });
155
+ mockResolvePrToken.mockReturnValue('gh-token');
156
+ mockPostSummaryComment.mockResolvedValue({ createdNativeId: 7, deletedNativeIds: [] });
157
+ const { reviewCommand } = await import('./review.js');
158
+ await reviewCommand('.', defaultOptions({ pr: 42, outputFormat: 'sarif' }));
159
+ expect(exitSpy).toHaveBeenCalledWith(1);
160
+ });
161
+ });
162
+ //# sourceMappingURL=review.pr-exit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.pr-exit.test.js","sourceRoot":"","sources":["../../src/commands/review.pr-exit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 3.3 — Jenkins + GitHub composition scenario.
3
+ *
4
+ * Simulates a CI/Jenkins run of `ghagga review --pr N --output sarif`:
5
+ * - GITHUB_TOKEN env is set (no interactive login) → the post-back resolves it
6
+ * and posts the summary via the forge adapter.
7
+ * - `--output sarif` still emits SARIF (buildSarif) — the two outputs coexist.
8
+ * - issue-export stays CLI-local: createIssue/ensureLabel are the SAME
9
+ * github-api.ts functions, untouched by the PR path.
10
+ *
11
+ * We assemble the exact building blocks reviewCommand composes (token resolution
12
+ * → adapter post-back, buildSarif, github-api issue fns) rather than driving the
13
+ * full LLM pipeline, so the scenario is deterministic and offline.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=review.pr-jenkins.test.d.ts.map