ghagga 2.1.0 → 2.3.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/README.md +41 -8
- package/dist/commands/hooks/index.d.ts +9 -0
- package/dist/commands/hooks/index.d.ts.map +1 -0
- package/dist/commands/hooks/index.js +15 -0
- package/dist/commands/hooks/index.js.map +1 -0
- package/dist/commands/hooks/install.d.ts +13 -0
- package/dist/commands/hooks/install.d.ts.map +1 -0
- package/dist/commands/hooks/install.js +64 -0
- package/dist/commands/hooks/install.js.map +1 -0
- package/dist/commands/hooks/install.test.d.ts +11 -0
- package/dist/commands/hooks/install.test.d.ts.map +1 -0
- package/dist/commands/hooks/install.test.js +169 -0
- package/dist/commands/hooks/install.test.js.map +1 -0
- package/dist/commands/hooks/status.d.ts +12 -0
- package/dist/commands/hooks/status.d.ts.map +1 -0
- package/dist/commands/hooks/status.js +38 -0
- package/dist/commands/hooks/status.js.map +1 -0
- package/dist/commands/hooks/status.test.d.ts +11 -0
- package/dist/commands/hooks/status.test.d.ts.map +1 -0
- package/dist/commands/hooks/status.test.js +153 -0
- package/dist/commands/hooks/status.test.js.map +1 -0
- package/dist/commands/hooks/uninstall.d.ts +12 -0
- package/dist/commands/hooks/uninstall.d.ts.map +1 -0
- package/dist/commands/hooks/uninstall.js +44 -0
- package/dist/commands/hooks/uninstall.js.map +1 -0
- package/dist/commands/hooks/uninstall.test.d.ts +10 -0
- package/dist/commands/hooks/uninstall.test.d.ts.map +1 -0
- package/dist/commands/hooks/uninstall.test.js +142 -0
- package/dist/commands/hooks/uninstall.test.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/login.test.js +5 -3
- package/dist/commands/login.test.js.map +1 -1
- package/dist/commands/logout.test.js +1 -1
- package/dist/commands/logout.test.js.map +1 -1
- package/dist/commands/memory/clear.d.ts +1 -1
- package/dist/commands/memory/clear.d.ts.map +1 -1
- package/dist/commands/memory/clear.js +1 -1
- package/dist/commands/memory/clear.js.map +1 -1
- package/dist/commands/memory/clear.test.js +5 -7
- package/dist/commands/memory/clear.test.js.map +1 -1
- package/dist/commands/memory/delete.d.ts +1 -1
- package/dist/commands/memory/delete.d.ts.map +1 -1
- package/dist/commands/memory/delete.js +2 -2
- package/dist/commands/memory/delete.js.map +1 -1
- package/dist/commands/memory/delete.test.js +5 -7
- package/dist/commands/memory/delete.test.js.map +1 -1
- package/dist/commands/memory/index.d.ts.map +1 -1
- package/dist/commands/memory/index.js +3 -4
- package/dist/commands/memory/index.js.map +1 -1
- package/dist/commands/memory/list.d.ts +1 -1
- package/dist/commands/memory/list.d.ts.map +1 -1
- package/dist/commands/memory/list.js +1 -1
- package/dist/commands/memory/list.js.map +1 -1
- package/dist/commands/memory/list.test.js +17 -5
- package/dist/commands/memory/list.test.js.map +1 -1
- package/dist/commands/memory/search.d.ts +1 -1
- package/dist/commands/memory/search.d.ts.map +1 -1
- package/dist/commands/memory/search.js +1 -1
- package/dist/commands/memory/search.js.map +1 -1
- package/dist/commands/memory/search.test.js +9 -4
- package/dist/commands/memory/search.test.js.map +1 -1
- package/dist/commands/memory/show.d.ts +1 -1
- package/dist/commands/memory/show.d.ts.map +1 -1
- package/dist/commands/memory/show.js +3 -5
- package/dist/commands/memory/show.js.map +1 -1
- package/dist/commands/memory/show.test.js +4 -6
- package/dist/commands/memory/show.test.js.map +1 -1
- package/dist/commands/memory/stats.d.ts +1 -1
- package/dist/commands/memory/stats.d.ts.map +1 -1
- package/dist/commands/memory/stats.js +3 -7
- package/dist/commands/memory/stats.js.map +1 -1
- package/dist/commands/memory/stats.test.js +3 -3
- package/dist/commands/memory/stats.test.js.map +1 -1
- package/dist/commands/memory/utils.d.ts.map +1 -1
- package/dist/commands/memory/utils.js +1 -1
- package/dist/commands/memory/utils.js.map +1 -1
- package/dist/commands/memory/utils.test.js +2 -2
- package/dist/commands/memory/utils.test.js.map +1 -1
- package/dist/commands/review-commit-msg.d.ts +28 -0
- package/dist/commands/review-commit-msg.d.ts.map +1 -0
- package/dist/commands/review-commit-msg.js +126 -0
- package/dist/commands/review-commit-msg.js.map +1 -0
- package/dist/commands/review-commit-msg.test.d.ts +11 -0
- package/dist/commands/review-commit-msg.test.d.ts.map +1 -0
- package/dist/commands/review-commit-msg.test.js +126 -0
- package/dist/commands/review-commit-msg.test.js.map +1 -0
- package/dist/commands/review.d.ts +7 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +107 -16
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/review.test.js +8 -7
- package/dist/commands/review.test.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/status.test.js +2 -2
- package/dist/commands/status.test.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -16
- package/dist/index.js.map +1 -1
- package/dist/lib/config.js +3 -3
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +11 -9
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/git-hooks.d.ts +19 -0
- package/dist/lib/git-hooks.d.ts.map +1 -0
- package/dist/lib/git-hooks.js +129 -0
- package/dist/lib/git-hooks.js.map +1 -0
- package/dist/lib/git-hooks.test.d.ts +11 -0
- package/dist/lib/git-hooks.test.d.ts.map +1 -0
- package/dist/lib/git-hooks.test.js +178 -0
- package/dist/lib/git-hooks.test.js.map +1 -0
- package/dist/lib/git.d.ts +10 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +33 -1
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/git.test.js +5 -3
- package/dist/lib/git.test.js.map +1 -1
- package/dist/lib/hook-templates.d.ts +12 -0
- package/dist/lib/hook-templates.d.ts.map +1 -0
- package/dist/lib/hook-templates.js +52 -0
- package/dist/lib/hook-templates.js.map +1 -0
- package/dist/lib/hook-templates.test.d.ts +11 -0
- package/dist/lib/hook-templates.test.d.ts.map +1 -0
- package/dist/lib/hook-templates.test.js +76 -0
- package/dist/lib/hook-templates.test.js.map +1 -0
- package/dist/lib/hooks-types.d.ts +30 -0
- package/dist/lib/hooks-types.d.ts.map +1 -0
- package/dist/lib/hooks-types.js +9 -0
- package/dist/lib/hooks-types.js.map +1 -0
- package/dist/lib/oauth.js +1 -1
- package/dist/lib/oauth.js.map +1 -1
- package/dist/lib/oauth.test.js +4 -3
- package/dist/lib/oauth.test.js.map +1 -1
- package/dist/ui/__tests__/format.test.js +36 -7
- package/dist/ui/__tests__/format.test.js.map +1 -1
- package/dist/ui/__tests__/theme.test.js +16 -7
- package/dist/ui/__tests__/theme.test.js.map +1 -1
- package/dist/ui/__tests__/tui.test.js +8 -8
- package/dist/ui/__tests__/tui.test.js.map +1 -1
- package/dist/ui/format.d.ts.map +1 -1
- package/dist/ui/format.js +6 -6
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +1 -1
- package/dist/ui/theme.js.map +1 -1
- package/dist/ui/tui.js +1 -1
- package/dist/ui/tui.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commit message review — lightweight validation + optional LLM review.
|
|
3
|
+
*
|
|
4
|
+
* Used by `ghagga review --commit-msg <file>` for the commit-msg hook.
|
|
5
|
+
* Validates basic commit message format (heuristics) and optionally
|
|
6
|
+
* calls a single LLM prompt for quality assessment.
|
|
7
|
+
*
|
|
8
|
+
* Returns a ReviewResult-compatible structure for consistent exit-code handling.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Strip comment lines (starting with #) from a commit message.
|
|
12
|
+
* Git includes these as hints in the COMMIT_EDITMSG file.
|
|
13
|
+
*/
|
|
14
|
+
function stripComments(message) {
|
|
15
|
+
return message
|
|
16
|
+
.split('\n')
|
|
17
|
+
.filter((line) => !line.startsWith('#'))
|
|
18
|
+
.join('\n')
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run heuristic validations on a commit message.
|
|
23
|
+
* Returns an array of findings (empty = message is fine).
|
|
24
|
+
*/
|
|
25
|
+
function validateHeuristics(message) {
|
|
26
|
+
const findings = [];
|
|
27
|
+
const cleaned = stripComments(message);
|
|
28
|
+
// Empty message
|
|
29
|
+
if (cleaned.length === 0) {
|
|
30
|
+
findings.push({
|
|
31
|
+
severity: 'high',
|
|
32
|
+
message: 'Commit message is empty',
|
|
33
|
+
suggestion: 'Write a descriptive commit message explaining the change',
|
|
34
|
+
});
|
|
35
|
+
return findings; // No point checking further
|
|
36
|
+
}
|
|
37
|
+
// Too short (likely meaningless like "fix" or "wip")
|
|
38
|
+
if (cleaned.length <= 3) {
|
|
39
|
+
findings.push({
|
|
40
|
+
severity: 'high',
|
|
41
|
+
message: `Commit message is too short (${cleaned.length} chars)`,
|
|
42
|
+
suggestion: 'Write a descriptive message explaining what changed and why',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const lines = cleaned.split('\n');
|
|
46
|
+
const subject = lines[0] ?? '';
|
|
47
|
+
// Subject line > 72 chars
|
|
48
|
+
if (subject.length > 72) {
|
|
49
|
+
findings.push({
|
|
50
|
+
severity: 'medium',
|
|
51
|
+
message: `Subject line is ${subject.length} characters (recommended max: 72)`,
|
|
52
|
+
suggestion: 'Keep the subject line concise; move details to the body',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Subject ends with period
|
|
56
|
+
if (subject.endsWith('.')) {
|
|
57
|
+
findings.push({
|
|
58
|
+
severity: 'low',
|
|
59
|
+
message: 'Subject line ends with a period',
|
|
60
|
+
suggestion: 'Remove the trailing period from the subject line',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Body not separated by blank line
|
|
64
|
+
if (lines.length > 1 && lines[1] !== '') {
|
|
65
|
+
findings.push({
|
|
66
|
+
severity: 'medium',
|
|
67
|
+
message: 'Body is not separated from subject by a blank line',
|
|
68
|
+
suggestion: 'Add a blank line between the subject and body',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return findings;
|
|
72
|
+
}
|
|
73
|
+
// ─── Main Function ──────────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Validate a commit message using heuristics and optional LLM review.
|
|
76
|
+
* Returns a ReviewResult for consistent exit-code and output handling.
|
|
77
|
+
*/
|
|
78
|
+
export async function reviewCommitMessage(opts) {
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
const findings = [];
|
|
81
|
+
// ── Step 1: Heuristic validation ──────────────────────────
|
|
82
|
+
const heuristicFindings = validateHeuristics(opts.message);
|
|
83
|
+
for (const hf of heuristicFindings) {
|
|
84
|
+
findings.push({
|
|
85
|
+
severity: hf.severity,
|
|
86
|
+
category: hf.severity === 'low' ? 'style' : 'convention',
|
|
87
|
+
file: 'COMMIT_EDITMSG',
|
|
88
|
+
message: hf.message,
|
|
89
|
+
suggestion: hf.suggestion,
|
|
90
|
+
source: 'ai', // Use 'ai' as closest match in FindingSource union
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// ── Step 2: LLM review (skip in quick mode) ───────────────
|
|
94
|
+
// Note: LLM commit message review is a future enhancement.
|
|
95
|
+
// For now, heuristic validation covers the essential checks.
|
|
96
|
+
// When --quick is NOT set and we have a non-empty message,
|
|
97
|
+
// a future version will call a single LLM prompt here.
|
|
98
|
+
// ── Step 3: Determine status ──────────────────────────────
|
|
99
|
+
const hasBlockingIssues = findings.some((f) => f.severity === 'critical' || f.severity === 'high');
|
|
100
|
+
const status = hasBlockingIssues ? 'FAILED' : 'PASSED';
|
|
101
|
+
const executionTimeMs = Date.now() - startTime;
|
|
102
|
+
const summary = findings.length === 0
|
|
103
|
+
? 'Commit message looks good.'
|
|
104
|
+
: `Found ${findings.length} issue(s) in commit message.`;
|
|
105
|
+
return {
|
|
106
|
+
status,
|
|
107
|
+
summary,
|
|
108
|
+
findings,
|
|
109
|
+
staticAnalysis: {
|
|
110
|
+
semgrep: { status: 'skipped', findings: [], executionTimeMs: 0 },
|
|
111
|
+
trivy: { status: 'skipped', findings: [], executionTimeMs: 0 },
|
|
112
|
+
cpd: { status: 'skipped', findings: [], executionTimeMs: 0 },
|
|
113
|
+
},
|
|
114
|
+
memoryContext: null,
|
|
115
|
+
metadata: {
|
|
116
|
+
mode: 'simple',
|
|
117
|
+
provider: opts.quick ? 'none' : opts.provider,
|
|
118
|
+
model: opts.quick ? 'static-only' : opts.model,
|
|
119
|
+
tokensUsed: 0,
|
|
120
|
+
executionTimeMs,
|
|
121
|
+
toolsRun: [],
|
|
122
|
+
toolsSkipped: ['semgrep', 'trivy', 'cpd'],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=review-commit-msg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-commit-msg.js","sourceRoot":"","sources":["../../src/commands/review-commit-msg.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for commit message review (heuristic validation).
|
|
3
|
+
*
|
|
4
|
+
* Validates that reviewCommitMessage() correctly identifies
|
|
5
|
+
* empty, too-short, long subject, trailing period, missing blank line,
|
|
6
|
+
* git comments, and multiple issues. Also verifies quick mode skips AI.
|
|
7
|
+
*
|
|
8
|
+
* @see Phase 4, Test 3
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=review-commit-msg.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-commit-msg.test.d.ts","sourceRoot":"","sources":["../../src/commands/review-commit-msg.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for commit message review (heuristic validation).
|
|
3
|
+
*
|
|
4
|
+
* Validates that reviewCommitMessage() correctly identifies
|
|
5
|
+
* empty, too-short, long subject, trailing period, missing blank line,
|
|
6
|
+
* git comments, and multiple issues. Also verifies quick mode skips AI.
|
|
7
|
+
*
|
|
8
|
+
* @see Phase 4, Test 3
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import { reviewCommitMessage } from './review-commit-msg.js';
|
|
12
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
13
|
+
/** Default options for tests (quick mode — heuristics only) */
|
|
14
|
+
function makeOpts(message, overrides = {}) {
|
|
15
|
+
return {
|
|
16
|
+
message,
|
|
17
|
+
provider: 'anthropic',
|
|
18
|
+
model: 'claude-sonnet-4-20250514',
|
|
19
|
+
apiKey: 'test-key',
|
|
20
|
+
quick: true,
|
|
21
|
+
...overrides,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// ─── Tests ──────────────────────────────────────────────────────
|
|
25
|
+
describe('reviewCommitMessage — heuristic validation', () => {
|
|
26
|
+
it('flags empty message with high severity', async () => {
|
|
27
|
+
const result = await reviewCommitMessage(makeOpts(''));
|
|
28
|
+
expect(result.status).toBe('FAILED');
|
|
29
|
+
expect(result.findings).toHaveLength(1);
|
|
30
|
+
expect(result.findings[0]?.severity).toBe('high');
|
|
31
|
+
expect(result.findings[0]?.message).toContain('empty');
|
|
32
|
+
});
|
|
33
|
+
it('flags too-short message (≤ 3 chars) with high severity', async () => {
|
|
34
|
+
const result = await reviewCommitMessage(makeOpts('fix'));
|
|
35
|
+
expect(result.status).toBe('FAILED');
|
|
36
|
+
expect(result.findings.length).toBeGreaterThanOrEqual(1);
|
|
37
|
+
const shortFinding = result.findings.find((f) => f.message.includes('too short'));
|
|
38
|
+
expect(shortFinding).toBeDefined();
|
|
39
|
+
expect(shortFinding?.severity).toBe('high');
|
|
40
|
+
});
|
|
41
|
+
it('flags subject line > 72 chars with medium severity', async () => {
|
|
42
|
+
const longSubject = 'a'.repeat(80);
|
|
43
|
+
const result = await reviewCommitMessage(makeOpts(longSubject));
|
|
44
|
+
const finding = result.findings.find((f) => f.message.includes('72'));
|
|
45
|
+
expect(finding).toBeDefined();
|
|
46
|
+
expect(finding?.severity).toBe('medium');
|
|
47
|
+
});
|
|
48
|
+
it('flags subject ending with period with low severity', async () => {
|
|
49
|
+
const result = await reviewCommitMessage(makeOpts('Add new feature for users.'));
|
|
50
|
+
const finding = result.findings.find((f) => f.message.includes('period'));
|
|
51
|
+
expect(finding).toBeDefined();
|
|
52
|
+
expect(finding?.severity).toBe('low');
|
|
53
|
+
});
|
|
54
|
+
it('flags body not separated by blank line with medium severity', async () => {
|
|
55
|
+
const msg = 'Add feature\nThis is the body without blank line';
|
|
56
|
+
const result = await reviewCommitMessage(makeOpts(msg));
|
|
57
|
+
const finding = result.findings.find((f) => f.message.includes('blank line'));
|
|
58
|
+
expect(finding).toBeDefined();
|
|
59
|
+
expect(finding?.severity).toBe('medium');
|
|
60
|
+
});
|
|
61
|
+
it('returns no findings for a valid conventional commit', async () => {
|
|
62
|
+
const msg = 'feat(auth): add OAuth token refresh support';
|
|
63
|
+
const result = await reviewCommitMessage(makeOpts(msg));
|
|
64
|
+
expect(result.status).toBe('PASSED');
|
|
65
|
+
expect(result.findings).toHaveLength(0);
|
|
66
|
+
expect(result.summary).toContain('looks good');
|
|
67
|
+
});
|
|
68
|
+
it('strips git comment lines (# lines) before validation', async () => {
|
|
69
|
+
const msg = [
|
|
70
|
+
'feat: add new feature',
|
|
71
|
+
'',
|
|
72
|
+
'# Please enter the commit message',
|
|
73
|
+
'# Lines starting with # will be ignored.',
|
|
74
|
+
].join('\n');
|
|
75
|
+
const result = await reviewCommitMessage(makeOpts(msg));
|
|
76
|
+
expect(result.status).toBe('PASSED');
|
|
77
|
+
expect(result.findings).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
it('detects multiple issues simultaneously', async () => {
|
|
80
|
+
// Long subject + ends with period + body without blank line
|
|
81
|
+
const longSubject = `${'a'.repeat(80)}.`;
|
|
82
|
+
const msg = `${longSubject}\nBody without blank separator`;
|
|
83
|
+
const result = await reviewCommitMessage(makeOpts(msg));
|
|
84
|
+
// Should have at least 3 findings: long subject, period, no blank line
|
|
85
|
+
expect(result.findings.length).toBeGreaterThanOrEqual(3);
|
|
86
|
+
const severities = result.findings.map((f) => f.severity);
|
|
87
|
+
expect(severities).toContain('medium'); // long subject or blank line
|
|
88
|
+
expect(severities).toContain('low'); // period
|
|
89
|
+
});
|
|
90
|
+
it('uses "static-only" model in quick mode metadata', async () => {
|
|
91
|
+
const result = await reviewCommitMessage(makeOpts('feat: valid commit', { quick: true }));
|
|
92
|
+
expect(result.metadata.model).toBe('static-only');
|
|
93
|
+
expect(result.metadata.provider).toBe('none');
|
|
94
|
+
});
|
|
95
|
+
it('records actual provider and model when quick is false', async () => {
|
|
96
|
+
const result = await reviewCommitMessage(makeOpts('feat: valid commit', { quick: false }));
|
|
97
|
+
expect(result.metadata.provider).toBe('anthropic');
|
|
98
|
+
expect(result.metadata.model).toBe('claude-sonnet-4-20250514');
|
|
99
|
+
});
|
|
100
|
+
it('sets file to COMMIT_EDITMSG in all findings', async () => {
|
|
101
|
+
const result = await reviewCommitMessage(makeOpts(''));
|
|
102
|
+
for (const finding of result.findings) {
|
|
103
|
+
expect(finding.file).toBe('COMMIT_EDITMSG');
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
it('skips all static analysis tools', async () => {
|
|
107
|
+
const result = await reviewCommitMessage(makeOpts('feat: valid'));
|
|
108
|
+
expect(result.staticAnalysis?.semgrep.status).toBe('skipped');
|
|
109
|
+
expect(result.staticAnalysis?.trivy.status).toBe('skipped');
|
|
110
|
+
expect(result.staticAnalysis?.cpd.status).toBe('skipped');
|
|
111
|
+
});
|
|
112
|
+
it('returns PASSED status when only low-severity findings exist', async () => {
|
|
113
|
+
const result = await reviewCommitMessage(makeOpts('Add new feature for users.'));
|
|
114
|
+
// Only low severity (period) — should still pass
|
|
115
|
+
const hasCriticalOrHigh = result.findings.some((f) => f.severity === 'critical' || f.severity === 'high');
|
|
116
|
+
expect(hasCriticalOrHigh).toBe(false);
|
|
117
|
+
expect(result.status).toBe('PASSED');
|
|
118
|
+
});
|
|
119
|
+
it('includes valid body separated by blank line without findings', async () => {
|
|
120
|
+
const msg = 'feat(core): add caching layer\n\nThis adds Redis-based caching.';
|
|
121
|
+
const result = await reviewCommitMessage(makeOpts(msg));
|
|
122
|
+
expect(result.status).toBe('PASSED');
|
|
123
|
+
expect(result.findings).toHaveLength(0);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=review-commit-msg.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-commit-msg.test.js","sourceRoot":"","sources":["../../src/commands/review-commit-msg.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,mEAAmE;AAEnE,+DAA+D;AAC/D,SAAS,QAAQ,CACf,OAAe,EACf,YAA6C,EAAE;IAE/C,OAAO;QACL,OAAO;QACP,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,0BAA0B;QACjC,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,IAAI;QACX,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,mEAAmE;AAEnE,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAEvD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAE1D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;QAClF,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACnC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;QAEhE,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC,CAAC;QAEjF,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1E,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,GAAG,GAAG,kDAAkD,CAAC;QAC/D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAExD,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC;QAC9E,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9B,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,GAAG,GAAG,6CAA6C,CAAC;QAC1D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAExD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,GAAG,GAAG;YACV,uBAAuB;YACvB,EAAE;YACF,mCAAmC;YACnC,0CAA0C;SAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAExD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,4DAA4D;QAC5D,MAAM,WAAW,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC;QACzC,MAAM,GAAG,GAAG,GAAG,WAAW,gCAAgC,CAAC;QAC3D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAExD,uEAAuE;QACvE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QAEzD,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC1D,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,6BAA6B;QACrE,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE1F,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAClD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAE3F,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAEvD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;QAElE,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;QAC3E,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC,CAAC;QAEjF,iDAAiD;QACjD,MAAM,iBAAiB,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAC5C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAC1D,CAAC;QACF,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,GAAG,GAAG,iEAAiE,CAAC;QAC9E,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAExD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* environment, and optional .ghagga.json file, then runs the
|
|
6
6
|
* core review pipeline and formats the output.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { LLMProvider, ReviewMode } from 'ghagga-core';
|
|
9
9
|
export interface ReviewOptions {
|
|
10
10
|
mode: ReviewMode;
|
|
11
11
|
provider: LLMProvider;
|
|
@@ -16,8 +16,14 @@ export interface ReviewOptions {
|
|
|
16
16
|
trivy: boolean;
|
|
17
17
|
cpd: boolean;
|
|
18
18
|
memory: boolean;
|
|
19
|
+
/** Memory backend: 'sqlite' (default) or 'engram' */
|
|
20
|
+
memoryBackend?: 'sqlite' | 'engram';
|
|
19
21
|
config?: string;
|
|
20
22
|
verbose: boolean;
|
|
23
|
+
staged?: boolean;
|
|
24
|
+
commitMsg?: string;
|
|
25
|
+
exitOnIssues?: boolean;
|
|
26
|
+
quick?: boolean;
|
|
21
27
|
}
|
|
22
28
|
export declare function reviewCommand(targetPath: string, options: ReviewOptions): Promise<void>;
|
|
23
29
|
//# sourceMappingURL=review.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EACV,WAAW,EAIX,UAAU,EAIX,MAAM,aAAa,CAAC;AAgBrB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,WAAW,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,aAAa,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IAEjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAgBD,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkL7F"}
|
package/dist/commands/review.js
CHANGED
|
@@ -6,23 +6,64 @@
|
|
|
6
6
|
* core review pipeline and formats the output.
|
|
7
7
|
*/
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { resolveProjectId } from '../lib/git.js';
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { join, resolve } from 'node:path';
|
|
11
|
+
import { DEFAULT_SETTINGS, EngramMemoryStorage, reviewPipeline, SqliteMemoryStorage, } from 'ghagga-core';
|
|
13
12
|
import { getConfigDir } from '../lib/config.js';
|
|
14
|
-
import
|
|
13
|
+
import { getStagedDiff, resolveProjectId } from '../lib/git.js';
|
|
15
14
|
import { formatMarkdownResult } from '../ui/format.js';
|
|
16
15
|
import { resolveStepIcon } from '../ui/theme.js';
|
|
16
|
+
import * as tui from '../ui/tui.js';
|
|
17
|
+
import { reviewCommitMessage } from './review-commit-msg.js';
|
|
17
18
|
// ─── Main Command ───────────────────────────────────────────────
|
|
18
19
|
export async function reviewCommand(targetPath, options) {
|
|
19
20
|
const repoPath = resolve(targetPath);
|
|
20
21
|
let memoryStorage;
|
|
21
22
|
try {
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// ── Mutual exclusivity check: --staged and --commit-msg ──
|
|
24
|
+
if (options.staged && options.commitMsg) {
|
|
25
|
+
tui.log.error('❌ --staged and --commit-msg are mutually exclusive. Use one or the other.');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// ── Commit message review path (bypasses file-based pipeline) ──
|
|
29
|
+
if (options.commitMsg) {
|
|
30
|
+
const commitMsgFile = resolve(options.commitMsg);
|
|
31
|
+
if (!existsSync(commitMsgFile)) {
|
|
32
|
+
tui.log.error(`❌ Commit message file not found: ${commitMsgFile}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const message = readFileSync(commitMsgFile, 'utf-8');
|
|
36
|
+
if (options.format !== 'json') {
|
|
37
|
+
tui.intro('🤖 GHAGGA Commit Message Review');
|
|
38
|
+
}
|
|
39
|
+
const result = await reviewCommitMessage({
|
|
40
|
+
message,
|
|
41
|
+
provider: options.provider,
|
|
42
|
+
model: options.model,
|
|
43
|
+
apiKey: options.apiKey,
|
|
44
|
+
quick: options.quick,
|
|
45
|
+
});
|
|
46
|
+
// Output the result
|
|
47
|
+
if (options.format === 'json') {
|
|
48
|
+
console.log(JSON.stringify(result, null, 2));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
tui.log.message(formatMarkdownResult(result));
|
|
52
|
+
}
|
|
53
|
+
// Exit code: --exit-on-issues overrides default behavior
|
|
54
|
+
const exitCode = resolveExitCode(result, options.exitOnIssues ?? false);
|
|
55
|
+
if (options.format !== 'json') {
|
|
56
|
+
tui.outro('Commit message review complete');
|
|
57
|
+
}
|
|
58
|
+
process.exit(exitCode);
|
|
59
|
+
}
|
|
60
|
+
// ── Step 1: Get the git diff ─────────────────────────────
|
|
61
|
+
const diff = options.staged ? getStagedDiff(repoPath) : getGitDiff(repoPath);
|
|
24
62
|
if (!diff || diff.trim().length === 0) {
|
|
25
|
-
|
|
63
|
+
const msg = options.staged
|
|
64
|
+
? 'ℹ️ No staged changes found. Stage files with `git add` first.'
|
|
65
|
+
: 'ℹ️ No changes detected. Stage some changes or make commits to review.';
|
|
66
|
+
tui.log.info(msg);
|
|
26
67
|
process.exit(0);
|
|
27
68
|
}
|
|
28
69
|
// Step 2: Load optional config file
|
|
@@ -33,14 +74,49 @@ export async function reviewCommand(targetPath, options) {
|
|
|
33
74
|
if (options.format !== 'json') {
|
|
34
75
|
tui.intro('🤖 GHAGGA Code Review');
|
|
35
76
|
tui.log.message(` Mode: ${options.mode} | Provider: ${options.provider} | Model: ${options.model}`);
|
|
36
|
-
|
|
77
|
+
if (options.staged) {
|
|
78
|
+
tui.log.step(' Reviewing staged changes...\n');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
tui.log.step(' Analyzing...\n');
|
|
82
|
+
}
|
|
37
83
|
}
|
|
38
|
-
// Step 4.5: Initialize memory storage
|
|
84
|
+
// Step 4.5: Initialize memory storage
|
|
39
85
|
const repoFullName = resolveProjectId(repoPath);
|
|
40
86
|
if (options.memory) {
|
|
87
|
+
// Determine backend: CLI flag > env var > default ('sqlite')
|
|
88
|
+
const memoryBackend = options.memoryBackend ??
|
|
89
|
+
process.env.GHAGGA_MEMORY_BACKEND ??
|
|
90
|
+
'sqlite';
|
|
91
|
+
// Validate backend value
|
|
92
|
+
const validBackends = ['sqlite', 'engram'];
|
|
93
|
+
if (!validBackends.includes(memoryBackend)) {
|
|
94
|
+
tui.log.error(`❌ Invalid memory backend "${memoryBackend}". Choose from: ${validBackends.join(', ')}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
41
97
|
try {
|
|
42
98
|
const dbPath = join(getConfigDir(), 'memory.db');
|
|
43
|
-
|
|
99
|
+
if (memoryBackend === 'engram') {
|
|
100
|
+
// Try Engram; fall back to SQLite if unavailable
|
|
101
|
+
const engramHost = process.env.GHAGGA_ENGRAM_HOST ?? 'http://localhost:7437';
|
|
102
|
+
const engramTimeout = process.env.GHAGGA_ENGRAM_TIMEOUT
|
|
103
|
+
? Number(process.env.GHAGGA_ENGRAM_TIMEOUT) * 1000
|
|
104
|
+
: undefined;
|
|
105
|
+
const engramStorage = await EngramMemoryStorage.create({
|
|
106
|
+
host: engramHost,
|
|
107
|
+
...(engramTimeout != null ? { timeout: engramTimeout } : {}),
|
|
108
|
+
});
|
|
109
|
+
if (engramStorage) {
|
|
110
|
+
memoryStorage = engramStorage;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
tui.log.warn('⚠️ Engram not available, falling back to SQLite memory');
|
|
114
|
+
memoryStorage = await SqliteMemoryStorage.create(dbPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
memoryStorage = await SqliteMemoryStorage.create(dbPath);
|
|
119
|
+
}
|
|
44
120
|
}
|
|
45
121
|
catch (error) {
|
|
46
122
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -67,6 +143,8 @@ export async function reviewCommand(targetPath, options) {
|
|
|
67
143
|
},
|
|
68
144
|
memoryStorage,
|
|
69
145
|
onProgress,
|
|
146
|
+
// --quick: disable AI review, use static analysis only
|
|
147
|
+
...(options.quick ? { aiReviewEnabled: false } : {}),
|
|
70
148
|
});
|
|
71
149
|
// Step 5.5: Persist memory to disk
|
|
72
150
|
await memoryStorage?.close();
|
|
@@ -77,8 +155,8 @@ export async function reviewCommand(targetPath, options) {
|
|
|
77
155
|
else {
|
|
78
156
|
tui.log.message(formatMarkdownResult(result));
|
|
79
157
|
}
|
|
80
|
-
// Step 7: Exit code
|
|
81
|
-
const exitCode =
|
|
158
|
+
// Step 7: Exit code — --exit-on-issues overrides default behavior
|
|
159
|
+
const exitCode = resolveExitCode(result, options.exitOnIssues ?? false);
|
|
82
160
|
if (options.format !== 'json') {
|
|
83
161
|
tui.outro('Review complete');
|
|
84
162
|
}
|
|
@@ -133,9 +211,7 @@ function getGitDiff(repoPath) {
|
|
|
133
211
|
* Load and parse an optional .ghagga.json config file.
|
|
134
212
|
*/
|
|
135
213
|
function loadConfigFile(repoPath, configPath) {
|
|
136
|
-
const filePath = configPath
|
|
137
|
-
? resolve(configPath)
|
|
138
|
-
: join(repoPath, '.ghagga.json');
|
|
214
|
+
const filePath = configPath ? resolve(configPath) : join(repoPath, '.ghagga.json');
|
|
139
215
|
if (!existsSync(filePath)) {
|
|
140
216
|
return {};
|
|
141
217
|
}
|
|
@@ -187,6 +263,21 @@ function createProgressHandler() {
|
|
|
187
263
|
};
|
|
188
264
|
}
|
|
189
265
|
// ─── Exit Code ──────────────────────────────────────────────────
|
|
266
|
+
/**
|
|
267
|
+
* Resolve the exit code for the review process.
|
|
268
|
+
*
|
|
269
|
+
* When `exitOnIssues` is true (hook mode), checks findings for
|
|
270
|
+
* critical/high severity — returns 1 if any found, 0 otherwise.
|
|
271
|
+
* When false, delegates to the default status-based exit code.
|
|
272
|
+
*/
|
|
273
|
+
function resolveExitCode(result, exitOnIssues) {
|
|
274
|
+
if (exitOnIssues) {
|
|
275
|
+
const hasBlockingIssues = result.findings.some((f) => f.severity === 'critical' || f.severity === 'high');
|
|
276
|
+
return hasBlockingIssues ? 1 : 0;
|
|
277
|
+
}
|
|
278
|
+
// Default behavior: use status-based exit code
|
|
279
|
+
return getExitCode(result.status);
|
|
280
|
+
}
|
|
190
281
|
/**
|
|
191
282
|
* Map review status to process exit code.
|
|
192
283
|
* PASSED and SKIPPED = 0, everything else = 1.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* and exit code mapping. The reviewPipeline is mocked to avoid
|
|
6
6
|
* needing an actual LLM API key.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
9
9
|
// ─── Mock ghagga-core to prevent actual LLM calls ───────────────
|
|
10
10
|
vi.mock('ghagga-core', () => ({
|
|
11
11
|
reviewPipeline: vi.fn(),
|
|
@@ -212,16 +212,17 @@ function makeReviewResult(overrides = {}) {
|
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
214
|
describe('reviewCommand — functional tests', () => {
|
|
215
|
-
//
|
|
215
|
+
// biome-ignore lint/suspicious/noExplicitAny: mock spy type
|
|
216
216
|
let logSpy;
|
|
217
|
-
//
|
|
217
|
+
// biome-ignore lint/suspicious/noExplicitAny: mock spy type
|
|
218
218
|
let errorSpy;
|
|
219
|
-
//
|
|
219
|
+
// biome-ignore lint/suspicious/noExplicitAny: mock spy type
|
|
220
220
|
let exitSpy;
|
|
221
221
|
beforeEach(() => {
|
|
222
222
|
vi.clearAllMocks();
|
|
223
223
|
logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
224
224
|
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
225
|
+
// biome-ignore lint/suspicious/noExplicitAny: mock cast
|
|
225
226
|
exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
226
227
|
mockExistsSync.mockReturnValue(false); // no .ghagga.json by default
|
|
227
228
|
});
|
|
@@ -376,7 +377,7 @@ describe('reviewCommand — functional tests', () => {
|
|
|
376
377
|
const { reviewCommand } = await import('./review.js');
|
|
377
378
|
await reviewCommand('.', defaultOptions());
|
|
378
379
|
// reviewPipeline should have been called with settings that include the config
|
|
379
|
-
const callArgs = mockReviewPipeline.mock.calls[0][0];
|
|
380
|
+
const callArgs = mockReviewPipeline.mock.calls[0]?.[0];
|
|
380
381
|
const settings = callArgs.settings;
|
|
381
382
|
expect(settings.reviewLevel).toBe('strict');
|
|
382
383
|
expect(settings.customRules).toEqual(['/rules/custom.yml']);
|
|
@@ -402,7 +403,7 @@ describe('reviewCommand — functional tests', () => {
|
|
|
402
403
|
mockReviewPipeline.mockResolvedValue(makeReviewResult());
|
|
403
404
|
const { reviewCommand } = await import('./review.js');
|
|
404
405
|
await reviewCommand('.', defaultOptions({ verbose: true }));
|
|
405
|
-
const callArgs = mockReviewPipeline.mock.calls[0][0];
|
|
406
|
+
const callArgs = mockReviewPipeline.mock.calls[0]?.[0];
|
|
406
407
|
expect(callArgs.onProgress).toBeTypeOf('function');
|
|
407
408
|
});
|
|
408
409
|
it('should not pass progress handler when verbose is false', async () => {
|
|
@@ -410,7 +411,7 @@ describe('reviewCommand — functional tests', () => {
|
|
|
410
411
|
mockReviewPipeline.mockResolvedValue(makeReviewResult());
|
|
411
412
|
const { reviewCommand } = await import('./review.js');
|
|
412
413
|
await reviewCommand('.', defaultOptions({ verbose: false }));
|
|
413
|
-
const callArgs = mockReviewPipeline.mock.calls[0][0];
|
|
414
|
+
const callArgs = mockReviewPipeline.mock.calls[0]?.[0];
|
|
414
415
|
expect(callArgs.onProgress).toBeUndefined();
|
|
415
416
|
});
|
|
416
417
|
it('should print progress header with mode, provider, and model', async () => {
|