ghagga 3.0.0 → 3.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.
- package/dist/commands/login.test.js +0 -2
- package/dist/commands/login.test.js.map +1 -1
- package/dist/commands/review-output.test.js +28 -0
- package/dist/commands/review-output.test.js.map +1 -1
- package/dist/commands/review.d.ts +12 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +225 -3
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/review.mr-exit.test.d.ts +13 -0
- package/dist/commands/review.mr-exit.test.d.ts.map +1 -0
- package/dist/commands/review.mr-exit.test.js +190 -0
- package/dist/commands/review.mr-exit.test.js.map +1 -0
- package/dist/commands/review.postback-disclosure.test.d.ts +22 -0
- package/dist/commands/review.postback-disclosure.test.d.ts.map +1 -0
- package/dist/commands/review.postback-disclosure.test.js +192 -0
- package/dist/commands/review.postback-disclosure.test.js.map +1 -0
- package/dist/commands/review.pr-exit.test.d.ts +13 -0
- package/dist/commands/review.pr-exit.test.d.ts.map +1 -0
- package/dist/commands/review.pr-exit.test.js +162 -0
- package/dist/commands/review.pr-exit.test.js.map +1 -0
- package/dist/commands/review.pr-jenkins.test.d.ts +16 -0
- package/dist/commands/review.pr-jenkins.test.d.ts.map +1 -0
- package/dist/commands/review.pr-jenkins.test.js +106 -0
- package/dist/commands/review.pr-jenkins.test.js.map +1 -0
- package/dist/commands/review.pr-nativeid-fallback.test.d.ts +15 -0
- package/dist/commands/review.pr-nativeid-fallback.test.d.ts.map +1 -0
- package/dist/commands/review.pr-nativeid-fallback.test.js +151 -0
- package/dist/commands/review.pr-nativeid-fallback.test.js.map +1 -0
- package/dist/commands/review.test.js +4 -0
- package/dist/commands/review.test.js.map +1 -1
- package/dist/index.js +39 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/cli-github-client-port.d.ts +52 -0
- package/dist/lib/cli-github-client-port.d.ts.map +1 -0
- package/dist/lib/cli-github-client-port.js +199 -0
- package/dist/lib/cli-github-client-port.js.map +1 -0
- package/dist/lib/cli-github-client-port.test.d.ts +10 -0
- package/dist/lib/cli-github-client-port.test.d.ts.map +1 -0
- package/dist/lib/cli-github-client-port.test.js +47 -0
- package/dist/lib/cli-github-client-port.test.js.map +1 -0
- package/dist/lib/cli-gitlab-client-port.d.ts +62 -0
- package/dist/lib/cli-gitlab-client-port.d.ts.map +1 -0
- package/dist/lib/cli-gitlab-client-port.js +209 -0
- package/dist/lib/cli-gitlab-client-port.js.map +1 -0
- package/dist/lib/cli-gitlab-client-port.test.d.ts +9 -0
- package/dist/lib/cli-gitlab-client-port.test.d.ts.map +1 -0
- package/dist/lib/cli-gitlab-client-port.test.js +278 -0
- package/dist/lib/cli-gitlab-client-port.test.js.map +1 -0
- package/dist/lib/config.d.ts +12 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +9 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/forge-postback.d.ts +72 -0
- package/dist/lib/forge-postback.d.ts.map +1 -0
- package/dist/lib/forge-postback.js +39 -0
- package/dist/lib/forge-postback.js.map +1 -0
- package/dist/lib/gitlab-api.d.ts +50 -0
- package/dist/lib/gitlab-api.d.ts.map +1 -0
- package/dist/lib/gitlab-api.js +75 -0
- package/dist/lib/gitlab-api.js.map +1 -0
- package/dist/lib/gitlab-api.test.d.ts +2 -0
- package/dist/lib/gitlab-api.test.d.ts.map +1 -0
- package/dist/lib/gitlab-api.test.js +79 -0
- package/dist/lib/gitlab-api.test.js.map +1 -0
- package/dist/lib/gitlab-e2e.manual.test.d.ts +24 -0
- package/dist/lib/gitlab-e2e.manual.test.d.ts.map +1 -0
- package/dist/lib/gitlab-e2e.manual.test.js +57 -0
- package/dist/lib/gitlab-e2e.manual.test.js.map +1 -0
- package/dist/lib/gitlab-token.d.ts +17 -0
- package/dist/lib/gitlab-token.d.ts.map +1 -0
- package/dist/lib/gitlab-token.js +24 -0
- package/dist/lib/gitlab-token.js.map +1 -0
- package/dist/lib/gitlab-token.test.d.ts +2 -0
- package/dist/lib/gitlab-token.test.d.ts.map +1 -0
- package/dist/lib/gitlab-token.test.js +38 -0
- package/dist/lib/gitlab-token.test.js.map +1 -0
- package/dist/lib/oauth.test.js +0 -1
- package/dist/lib/oauth.test.js.map +1 -1
- package/dist/lib/pr-postback.d.ts +42 -0
- package/dist/lib/pr-postback.d.ts.map +1 -0
- package/dist/lib/pr-postback.js +44 -0
- package/dist/lib/pr-postback.js.map +1 -0
- package/dist/lib/pr-postback.test.d.ts +9 -0
- package/dist/lib/pr-postback.test.d.ts.map +1 -0
- package/dist/lib/pr-postback.test.js +196 -0
- package/dist/lib/pr-postback.test.js.map +1 -0
- package/dist/lib/pr-token.d.ts +15 -0
- package/dist/lib/pr-token.d.ts.map +1 -0
- package/dist/lib/pr-token.js +22 -0
- package/dist/lib/pr-token.js.map +1 -0
- package/dist/lib/pr-token.test.d.ts +2 -0
- package/dist/lib/pr-token.test.d.ts.map +1 -0
- package/dist/lib/pr-token.test.js +25 -0
- package/dist/lib/pr-token.test.js.map +1 -0
- package/package.json +8 -7
|
@@ -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
|