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.
Files changed (152) hide show
  1. package/README.md +41 -8
  2. package/dist/commands/hooks/index.d.ts +9 -0
  3. package/dist/commands/hooks/index.d.ts.map +1 -0
  4. package/dist/commands/hooks/index.js +15 -0
  5. package/dist/commands/hooks/index.js.map +1 -0
  6. package/dist/commands/hooks/install.d.ts +13 -0
  7. package/dist/commands/hooks/install.d.ts.map +1 -0
  8. package/dist/commands/hooks/install.js +64 -0
  9. package/dist/commands/hooks/install.js.map +1 -0
  10. package/dist/commands/hooks/install.test.d.ts +11 -0
  11. package/dist/commands/hooks/install.test.d.ts.map +1 -0
  12. package/dist/commands/hooks/install.test.js +169 -0
  13. package/dist/commands/hooks/install.test.js.map +1 -0
  14. package/dist/commands/hooks/status.d.ts +12 -0
  15. package/dist/commands/hooks/status.d.ts.map +1 -0
  16. package/dist/commands/hooks/status.js +38 -0
  17. package/dist/commands/hooks/status.js.map +1 -0
  18. package/dist/commands/hooks/status.test.d.ts +11 -0
  19. package/dist/commands/hooks/status.test.d.ts.map +1 -0
  20. package/dist/commands/hooks/status.test.js +153 -0
  21. package/dist/commands/hooks/status.test.js.map +1 -0
  22. package/dist/commands/hooks/uninstall.d.ts +12 -0
  23. package/dist/commands/hooks/uninstall.d.ts.map +1 -0
  24. package/dist/commands/hooks/uninstall.js +44 -0
  25. package/dist/commands/hooks/uninstall.js.map +1 -0
  26. package/dist/commands/hooks/uninstall.test.d.ts +10 -0
  27. package/dist/commands/hooks/uninstall.test.d.ts.map +1 -0
  28. package/dist/commands/hooks/uninstall.test.js +142 -0
  29. package/dist/commands/hooks/uninstall.test.js.map +1 -0
  30. package/dist/commands/login.d.ts.map +1 -1
  31. package/dist/commands/login.js +1 -1
  32. package/dist/commands/login.js.map +1 -1
  33. package/dist/commands/login.test.js +5 -3
  34. package/dist/commands/login.test.js.map +1 -1
  35. package/dist/commands/logout.test.js +1 -1
  36. package/dist/commands/logout.test.js.map +1 -1
  37. package/dist/commands/memory/clear.d.ts +1 -1
  38. package/dist/commands/memory/clear.d.ts.map +1 -1
  39. package/dist/commands/memory/clear.js +1 -1
  40. package/dist/commands/memory/clear.js.map +1 -1
  41. package/dist/commands/memory/clear.test.js +5 -7
  42. package/dist/commands/memory/clear.test.js.map +1 -1
  43. package/dist/commands/memory/delete.d.ts +1 -1
  44. package/dist/commands/memory/delete.d.ts.map +1 -1
  45. package/dist/commands/memory/delete.js +2 -2
  46. package/dist/commands/memory/delete.js.map +1 -1
  47. package/dist/commands/memory/delete.test.js +5 -7
  48. package/dist/commands/memory/delete.test.js.map +1 -1
  49. package/dist/commands/memory/index.d.ts.map +1 -1
  50. package/dist/commands/memory/index.js +3 -4
  51. package/dist/commands/memory/index.js.map +1 -1
  52. package/dist/commands/memory/list.d.ts +1 -1
  53. package/dist/commands/memory/list.d.ts.map +1 -1
  54. package/dist/commands/memory/list.js +1 -1
  55. package/dist/commands/memory/list.js.map +1 -1
  56. package/dist/commands/memory/list.test.js +17 -5
  57. package/dist/commands/memory/list.test.js.map +1 -1
  58. package/dist/commands/memory/search.d.ts +1 -1
  59. package/dist/commands/memory/search.d.ts.map +1 -1
  60. package/dist/commands/memory/search.js +1 -1
  61. package/dist/commands/memory/search.js.map +1 -1
  62. package/dist/commands/memory/search.test.js +9 -4
  63. package/dist/commands/memory/search.test.js.map +1 -1
  64. package/dist/commands/memory/show.d.ts +1 -1
  65. package/dist/commands/memory/show.d.ts.map +1 -1
  66. package/dist/commands/memory/show.js +3 -5
  67. package/dist/commands/memory/show.js.map +1 -1
  68. package/dist/commands/memory/show.test.js +4 -6
  69. package/dist/commands/memory/show.test.js.map +1 -1
  70. package/dist/commands/memory/stats.d.ts +1 -1
  71. package/dist/commands/memory/stats.d.ts.map +1 -1
  72. package/dist/commands/memory/stats.js +3 -7
  73. package/dist/commands/memory/stats.js.map +1 -1
  74. package/dist/commands/memory/stats.test.js +3 -3
  75. package/dist/commands/memory/stats.test.js.map +1 -1
  76. package/dist/commands/memory/utils.d.ts.map +1 -1
  77. package/dist/commands/memory/utils.js +1 -1
  78. package/dist/commands/memory/utils.js.map +1 -1
  79. package/dist/commands/memory/utils.test.js +2 -2
  80. package/dist/commands/memory/utils.test.js.map +1 -1
  81. package/dist/commands/review-commit-msg.d.ts +28 -0
  82. package/dist/commands/review-commit-msg.d.ts.map +1 -0
  83. package/dist/commands/review-commit-msg.js +126 -0
  84. package/dist/commands/review-commit-msg.js.map +1 -0
  85. package/dist/commands/review-commit-msg.test.d.ts +11 -0
  86. package/dist/commands/review-commit-msg.test.d.ts.map +1 -0
  87. package/dist/commands/review-commit-msg.test.js +126 -0
  88. package/dist/commands/review-commit-msg.test.js.map +1 -0
  89. package/dist/commands/review.d.ts +7 -1
  90. package/dist/commands/review.d.ts.map +1 -1
  91. package/dist/commands/review.js +107 -16
  92. package/dist/commands/review.js.map +1 -1
  93. package/dist/commands/review.test.js +8 -7
  94. package/dist/commands/review.test.js.map +1 -1
  95. package/dist/commands/status.js +1 -1
  96. package/dist/commands/status.js.map +1 -1
  97. package/dist/commands/status.test.js +2 -2
  98. package/dist/commands/status.test.js.map +1 -1
  99. package/dist/index.d.ts +5 -4
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +37 -16
  102. package/dist/index.js.map +1 -1
  103. package/dist/lib/config.js +3 -3
  104. package/dist/lib/config.js.map +1 -1
  105. package/dist/lib/config.test.js +11 -9
  106. package/dist/lib/config.test.js.map +1 -1
  107. package/dist/lib/git-hooks.d.ts +19 -0
  108. package/dist/lib/git-hooks.d.ts.map +1 -0
  109. package/dist/lib/git-hooks.js +129 -0
  110. package/dist/lib/git-hooks.js.map +1 -0
  111. package/dist/lib/git-hooks.test.d.ts +11 -0
  112. package/dist/lib/git-hooks.test.d.ts.map +1 -0
  113. package/dist/lib/git-hooks.test.js +178 -0
  114. package/dist/lib/git-hooks.test.js.map +1 -0
  115. package/dist/lib/git.d.ts +10 -0
  116. package/dist/lib/git.d.ts.map +1 -1
  117. package/dist/lib/git.js +33 -1
  118. package/dist/lib/git.js.map +1 -1
  119. package/dist/lib/git.test.js +5 -3
  120. package/dist/lib/git.test.js.map +1 -1
  121. package/dist/lib/hook-templates.d.ts +12 -0
  122. package/dist/lib/hook-templates.d.ts.map +1 -0
  123. package/dist/lib/hook-templates.js +52 -0
  124. package/dist/lib/hook-templates.js.map +1 -0
  125. package/dist/lib/hook-templates.test.d.ts +11 -0
  126. package/dist/lib/hook-templates.test.d.ts.map +1 -0
  127. package/dist/lib/hook-templates.test.js +76 -0
  128. package/dist/lib/hook-templates.test.js.map +1 -0
  129. package/dist/lib/hooks-types.d.ts +30 -0
  130. package/dist/lib/hooks-types.d.ts.map +1 -0
  131. package/dist/lib/hooks-types.js +9 -0
  132. package/dist/lib/hooks-types.js.map +1 -0
  133. package/dist/lib/oauth.js +1 -1
  134. package/dist/lib/oauth.js.map +1 -1
  135. package/dist/lib/oauth.test.js +4 -3
  136. package/dist/lib/oauth.test.js.map +1 -1
  137. package/dist/ui/__tests__/format.test.js +36 -7
  138. package/dist/ui/__tests__/format.test.js.map +1 -1
  139. package/dist/ui/__tests__/theme.test.js +16 -7
  140. package/dist/ui/__tests__/theme.test.js.map +1 -1
  141. package/dist/ui/__tests__/tui.test.js +8 -8
  142. package/dist/ui/__tests__/tui.test.js.map +1 -1
  143. package/dist/ui/format.d.ts.map +1 -1
  144. package/dist/ui/format.js +6 -6
  145. package/dist/ui/format.js.map +1 -1
  146. package/dist/ui/theme.d.ts +1 -1
  147. package/dist/ui/theme.d.ts.map +1 -1
  148. package/dist/ui/theme.js +1 -1
  149. package/dist/ui/theme.js.map +1 -1
  150. package/dist/ui/tui.js +1 -1
  151. package/dist/ui/tui.js.map +1 -1
  152. 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":"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"}
@@ -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 { ReviewMode, LLMProvider } from 'ghagga-core';
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;AAUH,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAMZ,MAAM,aAAa,CAAC;AASrB,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,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAgBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAuFf"}
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"}
@@ -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 { readFileSync, existsSync } from 'node:fs';
10
- import { resolve, join } from 'node:path';
11
- import { reviewPipeline, DEFAULT_SETTINGS, SqliteMemoryStorage, } from 'ghagga-core';
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 * as tui from '../ui/tui.js';
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
- // Step 1: Get the git diff
23
- const diff = getGitDiff(repoPath);
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
- tui.log.info('ℹ️ No changes detected. Stage some changes or make commits to review.');
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
- tui.log.step(' Analyzing...\n');
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 (SQLite, file-backed)
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
- memoryStorage = await SqliteMemoryStorage.create(dbPath);
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 based on status
81
- const exitCode = getExitCode(result.status);
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":"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"}
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 { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
+ // biome-ignore lint/suspicious/noExplicitAny: mock spy type
216
216
  let logSpy;
217
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ // biome-ignore lint/suspicious/noExplicitAny: mock spy type
218
218
  let errorSpy;
219
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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 () => {