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 @@
1
+ {"version":3,"file":"git-hooks.js","sourceRoot":"","sources":["../../src/lib/git-hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for git hook utilities (install, uninstall, status).
3
+ *
4
+ * Mocks node:fs, node:child_process, and node:path to test
5
+ * isGitRepo, getHooksDir, getHookStatus, installHook, and uninstallHook
6
+ * without touching the real filesystem or git.
7
+ *
8
+ * @see Phase 4, Test 2
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=git-hooks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-hooks.test.d.ts","sourceRoot":"","sources":["../../src/lib/git-hooks.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Tests for git hook utilities (install, uninstall, status).
3
+ *
4
+ * Mocks node:fs, node:child_process, and node:path to test
5
+ * isGitRepo, getHooksDir, getHookStatus, installHook, and uninstallHook
6
+ * without touching the real filesystem or git.
7
+ *
8
+ * @see Phase 4, Test 2
9
+ */
10
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
11
+ import { HOOK_MARKER } from './hooks-types.js';
12
+ // ─── Mocks ──────────────────────────────────────────────────────
13
+ const { mockExecSync, mockExistsSync, mockReadFileSync, mockWriteFileSync, mockUnlinkSync, mockRenameSync, mockChmodSync, } = vi.hoisted(() => ({
14
+ mockExecSync: vi.fn(),
15
+ mockExistsSync: vi.fn(),
16
+ mockReadFileSync: vi.fn(),
17
+ mockWriteFileSync: vi.fn(),
18
+ mockUnlinkSync: vi.fn(),
19
+ mockRenameSync: vi.fn(),
20
+ mockChmodSync: vi.fn(),
21
+ }));
22
+ vi.mock('node:child_process', () => ({
23
+ execSync: (...args) => mockExecSync(...args),
24
+ }));
25
+ vi.mock('node:fs', () => ({
26
+ existsSync: (...args) => mockExistsSync(...args),
27
+ readFileSync: (...args) => mockReadFileSync(...args),
28
+ writeFileSync: (...args) => mockWriteFileSync(...args),
29
+ unlinkSync: (...args) => mockUnlinkSync(...args),
30
+ renameSync: (...args) => mockRenameSync(...args),
31
+ chmodSync: (...args) => mockChmodSync(...args),
32
+ }));
33
+ import { getHookStatus, getHooksDir, installHook, isGitRepo, uninstallHook } from './git-hooks.js';
34
+ // ─── Setup ──────────────────────────────────────────────────────
35
+ beforeEach(() => {
36
+ vi.clearAllMocks();
37
+ });
38
+ // ─── isGitRepo ──────────────────────────────────────────────────
39
+ describe('isGitRepo', () => {
40
+ it('returns true when inside a git repository', () => {
41
+ mockExecSync.mockReturnValue('.git');
42
+ expect(isGitRepo()).toBe(true);
43
+ expect(mockExecSync).toHaveBeenCalledWith('git rev-parse --git-dir', { stdio: 'pipe' });
44
+ });
45
+ it('returns false when outside a git repository', () => {
46
+ mockExecSync.mockImplementation(() => {
47
+ throw new Error('not a git repository');
48
+ });
49
+ expect(isGitRepo()).toBe(false);
50
+ });
51
+ });
52
+ // ─── getHooksDir ────────────────────────────────────────────────
53
+ describe('getHooksDir', () => {
54
+ it('returns default .git/hooks when no core.hooksPath is set', () => {
55
+ mockExecSync
56
+ .mockImplementationOnce(() => {
57
+ // core.hooksPath fails (not set)
58
+ throw new Error('key does not exist');
59
+ })
60
+ .mockReturnValueOnce('.git\n'); // git rev-parse --git-dir
61
+ const result = getHooksDir();
62
+ expect(result).toMatch(/\.git[/\\]hooks$/);
63
+ });
64
+ it('respects core.hooksPath config when set', () => {
65
+ mockExecSync.mockReturnValueOnce('/custom/hooks-dir\n'); // core.hooksPath
66
+ const result = getHooksDir();
67
+ expect(result).toBe('/custom/hooks-dir');
68
+ });
69
+ });
70
+ // ─── getHookStatus ──────────────────────────────────────────────
71
+ describe('getHookStatus', () => {
72
+ it('returns not installed when hook file does not exist', () => {
73
+ mockExistsSync.mockReturnValue(false);
74
+ const status = getHookStatus('/repo/.git/hooks', 'pre-commit');
75
+ expect(status.installed).toBe(false);
76
+ expect(status.managedByGhagga).toBe(false);
77
+ expect(status.type).toBe('pre-commit');
78
+ });
79
+ it('returns managed when GHAGGA marker is found in hook file', () => {
80
+ mockExistsSync.mockReturnValue(true);
81
+ mockReadFileSync.mockReturnValue(`#!/bin/sh\n${HOOK_MARKER}\nexec ghagga review`);
82
+ const status = getHookStatus('/repo/.git/hooks', 'pre-commit');
83
+ expect(status.installed).toBe(true);
84
+ expect(status.managedByGhagga).toBe(true);
85
+ });
86
+ it('returns not managed when external hook exists (no marker)', () => {
87
+ mockExistsSync.mockReturnValue(true);
88
+ mockReadFileSync.mockReturnValue('#!/bin/sh\necho "custom hook"');
89
+ const status = getHookStatus('/repo/.git/hooks', 'commit-msg');
90
+ expect(status.installed).toBe(true);
91
+ expect(status.managedByGhagga).toBe(false);
92
+ expect(status.type).toBe('commit-msg');
93
+ });
94
+ });
95
+ // ─── installHook ────────────────────────────────────────────────
96
+ describe('installHook', () => {
97
+ const hookContent = `#!/bin/sh\n${HOOK_MARKER}\nexec ghagga review`;
98
+ it('creates a new hook file when none exists', () => {
99
+ mockExistsSync.mockReturnValue(false);
100
+ const result = installHook('/repo/.git/hooks', 'pre-commit', hookContent, false);
101
+ expect(result.success).toBe(true);
102
+ expect(result.type).toBe('pre-commit');
103
+ expect(result.message).toContain('Installed pre-commit hook');
104
+ expect(result.backedUp).toBeFalsy();
105
+ expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining('pre-commit'), hookContent, { mode: 0o755 });
106
+ expect(mockChmodSync).toHaveBeenCalled();
107
+ });
108
+ it('overwrites GHAGGA-managed hooks without --force', () => {
109
+ mockExistsSync.mockReturnValue(true);
110
+ mockReadFileSync.mockReturnValue(`#!/bin/sh\n${HOOK_MARKER}\nold content`);
111
+ const result = installHook('/repo/.git/hooks', 'pre-commit', hookContent, false);
112
+ expect(result.success).toBe(true);
113
+ expect(result.backedUp).toBeFalsy();
114
+ expect(mockWriteFileSync).toHaveBeenCalled();
115
+ // Should NOT have been backed up (renamed)
116
+ expect(mockRenameSync).not.toHaveBeenCalled();
117
+ });
118
+ it('refuses to overwrite external hooks without --force', () => {
119
+ mockExistsSync.mockReturnValue(true);
120
+ mockReadFileSync.mockReturnValue('#!/bin/sh\necho "external"');
121
+ const result = installHook('/repo/.git/hooks', 'pre-commit', hookContent, false);
122
+ expect(result.success).toBe(false);
123
+ expect(result.message).toContain('already exists');
124
+ expect(result.message).toContain('--force');
125
+ expect(mockWriteFileSync).not.toHaveBeenCalled();
126
+ });
127
+ it('backs up external hooks with --force and installs', () => {
128
+ mockExistsSync.mockReturnValue(true);
129
+ mockReadFileSync.mockReturnValue('#!/bin/sh\necho "external"');
130
+ const result = installHook('/repo/.git/hooks', 'pre-commit', hookContent, true);
131
+ expect(result.success).toBe(true);
132
+ expect(result.backedUp).toBe(true);
133
+ expect(result.message).toContain('backed up');
134
+ expect(result.message).toContain('.ghagga-backup');
135
+ expect(mockRenameSync).toHaveBeenCalledWith(expect.stringContaining('pre-commit'), expect.stringContaining('pre-commit.ghagga-backup'));
136
+ expect(mockWriteFileSync).toHaveBeenCalled();
137
+ });
138
+ });
139
+ // ─── uninstallHook ──────────────────────────────────────────────
140
+ describe('uninstallHook', () => {
141
+ it('removes GHAGGA-managed hooks', () => {
142
+ mockExistsSync
143
+ .mockReturnValueOnce(true) // hook exists
144
+ .mockReturnValueOnce(false); // no backup
145
+ mockReadFileSync.mockReturnValue(`#!/bin/sh\n${HOOK_MARKER}\nexec ghagga review`);
146
+ const result = uninstallHook('/repo/.git/hooks', 'pre-commit');
147
+ expect(result.success).toBe(true);
148
+ expect(result.message).toContain('Removed pre-commit hook');
149
+ expect(mockUnlinkSync).toHaveBeenCalled();
150
+ });
151
+ it('skips external hooks (not managed by GHAGGA)', () => {
152
+ mockExistsSync.mockReturnValue(true);
153
+ mockReadFileSync.mockReturnValue('#!/bin/sh\necho "external"');
154
+ const result = uninstallHook('/repo/.git/hooks', 'commit-msg');
155
+ expect(result.success).toBe(false);
156
+ expect(result.message).toContain('not managed by GHAGGA');
157
+ expect(mockUnlinkSync).not.toHaveBeenCalled();
158
+ });
159
+ it('restores backup when present after removing GHAGGA hook', () => {
160
+ mockExistsSync
161
+ .mockReturnValueOnce(true) // hook exists
162
+ .mockReturnValueOnce(true); // backup exists
163
+ mockReadFileSync.mockReturnValue(`#!/bin/sh\n${HOOK_MARKER}\nexec ghagga review`);
164
+ const result = uninstallHook('/repo/.git/hooks', 'pre-commit');
165
+ expect(result.success).toBe(true);
166
+ expect(result.message).toContain('restored previous hook from backup');
167
+ expect(mockUnlinkSync).toHaveBeenCalled();
168
+ expect(mockRenameSync).toHaveBeenCalledWith(expect.stringContaining('.ghagga-backup'), expect.stringContaining('pre-commit'));
169
+ });
170
+ it('handles not-installed case gracefully', () => {
171
+ mockExistsSync.mockReturnValue(false);
172
+ const result = uninstallHook('/repo/.git/hooks', 'pre-commit');
173
+ expect(result.success).toBe(true);
174
+ expect(result.message).toContain('not installed');
175
+ expect(mockUnlinkSync).not.toHaveBeenCalled();
176
+ });
177
+ });
178
+ //# sourceMappingURL=git-hooks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-hooks.test.js","sourceRoot":"","sources":["../../src/lib/git-hooks.test.ts"],"names":[],"mappings":"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"}
package/dist/lib/git.d.ts CHANGED
@@ -10,6 +10,16 @@
10
10
  * Falls back to "local/unknown" if no remote is configured.
11
11
  */
12
12
  export declare function resolveProjectId(repoPath: string): string;
13
+ /**
14
+ * Get diff of staged files only.
15
+ * Returns an empty string if there are no staged changes or on error.
16
+ */
17
+ export declare function getStagedDiff(repoPath: string): string;
18
+ /**
19
+ * Get list of staged file paths.
20
+ * Returns an empty array if there are no staged files or on error.
21
+ */
22
+ export declare function getStagedFiles(repoPath: string): string[];
13
23
  /**
14
24
  * Normalize a git remote URL to "owner/repo" format.
15
25
  *
@@ -1 +1 @@
1
- {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmBtD"}
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAUzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmBtD"}
package/dist/lib/git.js CHANGED
@@ -22,6 +22,38 @@ export function resolveProjectId(repoPath) {
22
22
  return 'local/unknown';
23
23
  }
24
24
  }
25
+ /**
26
+ * Get diff of staged files only.
27
+ * Returns an empty string if there are no staged changes or on error.
28
+ */
29
+ export function getStagedDiff(repoPath) {
30
+ try {
31
+ return execSync('git diff --cached', {
32
+ cwd: repoPath,
33
+ encoding: 'utf-8',
34
+ maxBuffer: 10 * 1024 * 1024,
35
+ });
36
+ }
37
+ catch {
38
+ return '';
39
+ }
40
+ }
41
+ /**
42
+ * Get list of staged file paths.
43
+ * Returns an empty array if there are no staged files or on error.
44
+ */
45
+ export function getStagedFiles(repoPath) {
46
+ try {
47
+ const output = execSync('git diff --cached --name-only', {
48
+ cwd: repoPath,
49
+ encoding: 'utf-8',
50
+ });
51
+ return output.trim().split('\n').filter(Boolean);
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
25
57
  /**
26
58
  * Normalize a git remote URL to "owner/repo" format.
27
59
  *
@@ -37,7 +69,7 @@ export function normalizeRemoteUrl(url) {
37
69
  cleaned = cleaned.replace(/\.git$/, '');
38
70
  // SSH format: git@host:owner/repo
39
71
  const sshMatch = cleaned.match(/^[\w-]+@[\w.-]+:(.+)$/);
40
- if (sshMatch)
72
+ if (sshMatch?.[1])
41
73
  return sshMatch[1];
42
74
  // HTTPS / SSH protocol: extract path after host
43
75
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,QAAQ,CAAC,2BAA2B,EAAE;YACtD,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,OAAO,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,OAAO,GAAG,GAAG,CAAC;IAElB,oBAAoB;IACpB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAExC,kCAAkC;IAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACxD,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAE,CAAC;IAElC,gDAAgD;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,uBAAuB;QACvB,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;QAC7C,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,QAAQ,CAAC,2BAA2B,EAAE;YACtD,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,OAAO,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,mBAAmB,EAAE;YACnC,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,OAAO;YACjB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;SAC5B,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,+BAA+B,EAAE;YACvD,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,OAAO,GAAG,GAAG,CAAC;IAElB,oBAAoB;IACpB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAExC,kCAAkC;IAClC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACxD,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEtC,gDAAgD;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,uBAAuB;QACvB,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;QAC7C,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC"}
@@ -1,10 +1,10 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  // ─── Mocks ──────────────────────────────────────────────────────
3
3
  vi.mock('node:child_process', () => ({
4
4
  execSync: vi.fn(),
5
5
  }));
6
6
  import { execSync } from 'node:child_process';
7
- import { resolveProjectId, normalizeRemoteUrl } from './git.js';
7
+ import { normalizeRemoteUrl, resolveProjectId } from './git.js';
8
8
  const mockExecSync = vi.mocked(execSync);
9
9
  // ─── Tests ──────────────────────────────────────────────────────
10
10
  describe('normalizeRemoteUrl', () => {
@@ -53,7 +53,9 @@ describe('resolveProjectId', () => {
53
53
  expect(resolveProjectId('/path/to/repo')).toBe('acme/widgets');
54
54
  });
55
55
  it('falls back to local/unknown when no remote is configured', () => {
56
- mockExecSync.mockImplementation(() => { throw new Error('fatal: No such remote'); });
56
+ mockExecSync.mockImplementation(() => {
57
+ throw new Error('fatal: No such remote');
58
+ });
57
59
  expect(resolveProjectId('/path/to/repo')).toBe('local/unknown');
58
60
  });
59
61
  it('passes repoPath as cwd to execSync', () => {
@@ -1 +1 @@
1
- {"version":3,"file":"git.test.js","sourceRoot":"","sources":["../../src/lib/git.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git.test.js","sourceRoot":"","sources":["../../src/lib/git.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Hook script templates for pre-commit and commit-msg hooks.
3
+ *
4
+ * Pure functions that return POSIX shell script strings.
5
+ * Each script includes the GHAGGA marker, a PATH check with
6
+ * graceful degradation, and the appropriate ghagga CLI invocation.
7
+ */
8
+ /** Generate the pre-commit hook script */
9
+ export declare function generatePreCommitHook(extraArgs?: string): string;
10
+ /** Generate the commit-msg hook script */
11
+ export declare function generateCommitMsgHook(extraArgs?: string): string;
12
+ //# sourceMappingURL=hook-templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-templates.d.ts","sourceRoot":"","sources":["../../src/lib/hook-templates.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,0CAA0C;AAC1C,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBhE;AAED,0CAA0C;AAC1C,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAiBhE"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Hook script templates for pre-commit and commit-msg hooks.
3
+ *
4
+ * Pure functions that return POSIX shell script strings.
5
+ * Each script includes the GHAGGA marker, a PATH check with
6
+ * graceful degradation, and the appropriate ghagga CLI invocation.
7
+ */
8
+ import { HOOK_MARKER } from './hooks-types.js';
9
+ /** Generate the pre-commit hook script */
10
+ export function generatePreCommitHook(extraArgs) {
11
+ const args = extraArgs ? ` ${extraArgs}` : '';
12
+ return `#!/bin/sh
13
+ ${HOOK_MARKER}
14
+ # Installed by: ghagga hooks install
15
+ # To remove: ghagga hooks uninstall
16
+
17
+ # Check if ghagga is available
18
+ if ! command -v ghagga >/dev/null 2>&1; then
19
+ echo "Warning: ghagga not found in PATH. Skipping pre-commit review."
20
+ echo "Install: npm install -g ghagga"
21
+ exit 0
22
+ fi
23
+
24
+ # Check if there are staged changes
25
+ if git diff --cached --quiet; then
26
+ exit 0
27
+ fi
28
+
29
+ # Run review on staged files
30
+ exec ghagga review --staged --plain --exit-on-issues${args}
31
+ `;
32
+ }
33
+ /** Generate the commit-msg hook script */
34
+ export function generateCommitMsgHook(extraArgs) {
35
+ const args = extraArgs ? ` ${extraArgs}` : '';
36
+ return `#!/bin/sh
37
+ ${HOOK_MARKER}
38
+ # Installed by: ghagga hooks install
39
+ # To remove: ghagga hooks uninstall
40
+
41
+ # Check if ghagga is available
42
+ if ! command -v ghagga >/dev/null 2>&1; then
43
+ echo "Warning: ghagga not found in PATH. Skipping commit message review."
44
+ echo "Install: npm install -g ghagga"
45
+ exit 0
46
+ fi
47
+
48
+ # Validate commit message
49
+ exec ghagga review --commit-msg "$1" --plain --exit-on-issues${args}
50
+ `;
51
+ }
52
+ //# sourceMappingURL=hook-templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-templates.js","sourceRoot":"","sources":["../../src/lib/hook-templates.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,0CAA0C;AAC1C,MAAM,UAAU,qBAAqB,CAAC,SAAkB;IACtD,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,OAAO;EACP,WAAW;;;;;;;;;;;;;;;;;sDAiByC,IAAI;CACzD,CAAC;AACF,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,qBAAqB,CAAC,SAAkB;IACtD,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,OAAO;EACP,WAAW;;;;;;;;;;;;+DAYkD,IAAI;CAClE,CAAC;AACF,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for hook script template generators.
3
+ *
4
+ * Validates that generatePreCommitHook() and generateCommitMsgHook()
5
+ * produce correct POSIX shell scripts with the GHAGGA marker,
6
+ * PATH checks, and appropriate CLI invocations.
7
+ *
8
+ * @see Phase 4, Test 1
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=hook-templates.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-templates.test.d.ts","sourceRoot":"","sources":["../../src/lib/hook-templates.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Tests for hook script template generators.
3
+ *
4
+ * Validates that generatePreCommitHook() and generateCommitMsgHook()
5
+ * produce correct POSIX shell scripts with the GHAGGA marker,
6
+ * PATH checks, and appropriate CLI invocations.
7
+ *
8
+ * @see Phase 4, Test 1
9
+ */
10
+ import { describe, expect, it } from 'vitest';
11
+ import { generateCommitMsgHook, generatePreCommitHook } from './hook-templates.js';
12
+ import { HOOK_MARKER } from './hooks-types.js';
13
+ // ─── pre-commit template ────────────────────────────────────────
14
+ describe('generatePreCommitHook', () => {
15
+ it('starts with #!/bin/sh shebang', () => {
16
+ const script = generatePreCommitHook();
17
+ expect(script.startsWith('#!/bin/sh\n')).toBe(true);
18
+ });
19
+ it('includes the GHAGGA marker comment', () => {
20
+ const script = generatePreCommitHook();
21
+ expect(script).toContain(HOOK_MARKER);
22
+ });
23
+ it('includes the ghagga review --staged command', () => {
24
+ const script = generatePreCommitHook();
25
+ expect(script).toContain('ghagga review --staged --plain --exit-on-issues');
26
+ });
27
+ it('includes PATH check with graceful fallback', () => {
28
+ const script = generatePreCommitHook();
29
+ expect(script).toContain('command -v ghagga');
30
+ expect(script).toContain('ghagga not found in PATH');
31
+ expect(script).toContain('exit 0');
32
+ });
33
+ it('includes empty staged check (git diff --cached --quiet)', () => {
34
+ const script = generatePreCommitHook();
35
+ expect(script).toContain('git diff --cached --quiet');
36
+ });
37
+ it('appends extra args when provided', () => {
38
+ const script = generatePreCommitHook('--quick');
39
+ expect(script).toContain('ghagga review --staged --plain --exit-on-issues --quick');
40
+ });
41
+ it('does not append extra args when not provided', () => {
42
+ const script = generatePreCommitHook();
43
+ // Should end the exec line without trailing args
44
+ expect(script).toContain('exec ghagga review --staged --plain --exit-on-issues\n');
45
+ });
46
+ });
47
+ // ─── commit-msg template ────────────────────────────────────────
48
+ describe('generateCommitMsgHook', () => {
49
+ it('starts with #!/bin/sh shebang', () => {
50
+ const script = generateCommitMsgHook();
51
+ expect(script.startsWith('#!/bin/sh\n')).toBe(true);
52
+ });
53
+ it('includes the GHAGGA marker comment', () => {
54
+ const script = generateCommitMsgHook();
55
+ expect(script).toContain(HOOK_MARKER);
56
+ });
57
+ it('includes the ghagga review --commit-msg command', () => {
58
+ const script = generateCommitMsgHook();
59
+ expect(script).toContain('ghagga review --commit-msg "$1" --plain --exit-on-issues');
60
+ });
61
+ it('includes PATH check with graceful fallback', () => {
62
+ const script = generateCommitMsgHook();
63
+ expect(script).toContain('command -v ghagga');
64
+ expect(script).toContain('ghagga not found in PATH');
65
+ expect(script).toContain('exit 0');
66
+ });
67
+ it('appends extra args when provided', () => {
68
+ const script = generateCommitMsgHook('--quick');
69
+ expect(script).toContain('ghagga review --commit-msg "$1" --plain --exit-on-issues --quick');
70
+ });
71
+ it('does not append extra args when not provided', () => {
72
+ const script = generateCommitMsgHook();
73
+ expect(script).toContain('exec ghagga review --commit-msg "$1" --plain --exit-on-issues\n');
74
+ });
75
+ });
76
+ //# sourceMappingURL=hook-templates.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-templates.test.js","sourceRoot":"","sources":["../../src/lib/hook-templates.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Hook-related type definitions for the GHAGGA CLI.
3
+ *
4
+ * Used by git-hooks utilities, hook templates, and the hooks
5
+ * command group (install, uninstall, status).
6
+ */
7
+ /** Marker comment identifying GHAGGA-managed hooks */
8
+ export declare const HOOK_MARKER = "# GHAGGA-MANAGED-HOOK \u2014 do not edit";
9
+ /** Supported git hook types */
10
+ export type HookType = 'pre-commit' | 'commit-msg';
11
+ /** Result of a hook status check */
12
+ export interface HookStatus {
13
+ type: HookType;
14
+ installed: boolean;
15
+ managedByGhagga: boolean;
16
+ path: string;
17
+ }
18
+ /** Options for hook installation */
19
+ export interface HookInstallOptions {
20
+ force?: boolean;
21
+ }
22
+ /** Result of install/uninstall operations */
23
+ export interface HookOperationResult {
24
+ type: HookType;
25
+ success: boolean;
26
+ message: string;
27
+ /** Previous hook content if backed up */
28
+ backedUp?: boolean;
29
+ }
30
+ //# sourceMappingURL=hooks-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-types.d.ts","sourceRoot":"","sources":["../../src/lib/hooks-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sDAAsD;AACtD,eAAO,MAAM,WAAW,6CAAwC,CAAC;AAEjE,+BAA+B;AAC/B,MAAM,MAAM,QAAQ,GAAG,YAAY,GAAG,YAAY,CAAC;AAEnD,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Hook-related type definitions for the GHAGGA CLI.
3
+ *
4
+ * Used by git-hooks utilities, hook templates, and the hooks
5
+ * command group (install, uninstall, status).
6
+ */
7
+ /** Marker comment identifying GHAGGA-managed hooks */
8
+ export const HOOK_MARKER = '# GHAGGA-MANAGED-HOOK — do not edit';
9
+ //# sourceMappingURL=hooks-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-types.js","sourceRoot":"","sources":["../../src/lib/hooks-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,sDAAsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,qCAAqC,CAAC"}
package/dist/lib/oauth.js CHANGED
@@ -61,7 +61,7 @@ export async function pollForAccessToken(deviceCode, interval, expiresIn) {
61
61
  grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
62
62
  }),
63
63
  });
64
- const data = await response.json();
64
+ const data = (await response.json());
65
65
  // Check if we got a token
66
66
  if ('access_token' in data && data.access_token) {
67
67
  return data;
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/lib/oauth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/lib/oauth.ts"],"names":[],"mappings":"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"}
@@ -4,8 +4,8 @@
4
4
  * Tests requestDeviceCode(), pollForAccessToken(), and fetchGitHubUser()
5
5
  * with mocked global fetch and fake timers for the polling loop.
6
6
  */
7
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
8
- import { GITHUB_CLIENT_ID, requestDeviceCode, pollForAccessToken, fetchGitHubUser, } from './oauth.js';
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
8
+ import { fetchGitHubUser, GITHUB_CLIENT_ID, pollForAccessToken, requestDeviceCode, } from './oauth.js';
9
9
  // ─── Mock global fetch ──────────────────────────────────────────
10
10
  const mockFetch = vi.fn();
11
11
  vi.stubGlobal('fetch', mockFetch);
@@ -53,6 +53,7 @@ describe('requestDeviceCode', () => {
53
53
  }),
54
54
  }));
55
55
  // Verify client_id is in the body
56
+ // biome-ignore lint/style/noNonNullAssertion: test assertion on known mock data
56
57
  const callArgs = mockFetch.mock.calls[0];
57
58
  const body = JSON.parse(callArgs[1].body);
58
59
  expect(body.client_id).toBe(GITHUB_CLIENT_ID);
@@ -74,7 +75,7 @@ describe('pollForAccessToken', () => {
74
75
  vi.useRealTimers();
75
76
  });
76
77
  // Helper: run pollForAccessToken and advance timers
77
- async function runPoll(deviceCode = 'dc_123', interval = 1, expiresIn = 60) {
78
+ async function _runPoll(deviceCode = 'dc_123', interval = 1, expiresIn = 60) {
78
79
  const promise = pollForAccessToken(deviceCode, interval, expiresIn);
79
80
  // Advance through the initial sleep + poll cycles
80
81
  // We need to flush several timer rounds