ghagga 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +90 -19
  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 +16 -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 +157 -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 +123 -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 +120 -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 +17 -14
  32. package/dist/commands/login.js.map +1 -1
  33. package/dist/commands/login.test.d.ts +9 -0
  34. package/dist/commands/login.test.d.ts.map +1 -0
  35. package/dist/commands/login.test.js +138 -0
  36. package/dist/commands/login.test.js.map +1 -0
  37. package/dist/commands/logout.d.ts.map +1 -1
  38. package/dist/commands/logout.js +4 -3
  39. package/dist/commands/logout.js.map +1 -1
  40. package/dist/commands/logout.test.d.ts +8 -0
  41. package/dist/commands/logout.test.d.ts.map +1 -0
  42. package/dist/commands/logout.test.js +61 -0
  43. package/dist/commands/logout.test.js.map +1 -0
  44. package/dist/commands/memory/clear.d.ts +11 -0
  45. package/dist/commands/memory/clear.d.ts.map +1 -0
  46. package/dist/commands/memory/clear.js +45 -0
  47. package/dist/commands/memory/clear.js.map +1 -0
  48. package/dist/commands/memory/clear.test.d.ts +11 -0
  49. package/dist/commands/memory/clear.test.d.ts.map +1 -0
  50. package/dist/commands/memory/clear.test.js +178 -0
  51. package/dist/commands/memory/clear.test.js.map +1 -0
  52. package/dist/commands/memory/delete.d.ts +11 -0
  53. package/dist/commands/memory/delete.d.ts.map +1 -0
  54. package/dist/commands/memory/delete.js +38 -0
  55. package/dist/commands/memory/delete.js.map +1 -0
  56. package/dist/commands/memory/delete.test.d.ts +11 -0
  57. package/dist/commands/memory/delete.test.d.ts.map +1 -0
  58. package/dist/commands/memory/delete.test.js +176 -0
  59. package/dist/commands/memory/delete.test.js.map +1 -0
  60. package/dist/commands/memory/index.d.ts +10 -0
  61. package/dist/commands/memory/index.d.ts.map +1 -0
  62. package/dist/commands/memory/index.js +23 -0
  63. package/dist/commands/memory/index.js.map +1 -0
  64. package/dist/commands/memory/list.d.ts +11 -0
  65. package/dist/commands/memory/list.d.ts.map +1 -0
  66. package/dist/commands/memory/list.js +47 -0
  67. package/dist/commands/memory/list.js.map +1 -0
  68. package/dist/commands/memory/list.test.d.ts +10 -0
  69. package/dist/commands/memory/list.test.d.ts.map +1 -0
  70. package/dist/commands/memory/list.test.js +135 -0
  71. package/dist/commands/memory/list.test.js.map +1 -0
  72. package/dist/commands/memory/search.d.ts +12 -0
  73. package/dist/commands/memory/search.d.ts.map +1 -0
  74. package/dist/commands/memory/search.js +44 -0
  75. package/dist/commands/memory/search.js.map +1 -0
  76. package/dist/commands/memory/search.test.d.ts +11 -0
  77. package/dist/commands/memory/search.test.d.ts.map +1 -0
  78. package/dist/commands/memory/search.test.js +122 -0
  79. package/dist/commands/memory/search.test.js.map +1 -0
  80. package/dist/commands/memory/show.d.ts +10 -0
  81. package/dist/commands/memory/show.d.ts.map +1 -0
  82. package/dist/commands/memory/show.js +51 -0
  83. package/dist/commands/memory/show.js.map +1 -0
  84. package/dist/commands/memory/show.test.d.ts +11 -0
  85. package/dist/commands/memory/show.test.d.ts.map +1 -0
  86. package/dist/commands/memory/show.test.js +156 -0
  87. package/dist/commands/memory/show.test.js.map +1 -0
  88. package/dist/commands/memory/stats.d.ts +11 -0
  89. package/dist/commands/memory/stats.d.ts.map +1 -0
  90. package/dist/commands/memory/stats.js +63 -0
  91. package/dist/commands/memory/stats.js.map +1 -0
  92. package/dist/commands/memory/stats.test.d.ts +10 -0
  93. package/dist/commands/memory/stats.test.d.ts.map +1 -0
  94. package/dist/commands/memory/stats.test.js +122 -0
  95. package/dist/commands/memory/stats.test.js.map +1 -0
  96. package/dist/commands/memory/utils.d.ts +44 -0
  97. package/dist/commands/memory/utils.d.ts.map +1 -0
  98. package/dist/commands/memory/utils.js +90 -0
  99. package/dist/commands/memory/utils.js.map +1 -0
  100. package/dist/commands/memory/utils.test.d.ts +10 -0
  101. package/dist/commands/memory/utils.test.d.ts.map +1 -0
  102. package/dist/commands/memory/utils.test.js +93 -0
  103. package/dist/commands/memory/utils.test.js.map +1 -0
  104. package/dist/commands/review-commit-msg.d.ts +28 -0
  105. package/dist/commands/review-commit-msg.d.ts.map +1 -0
  106. package/dist/commands/review-commit-msg.js +126 -0
  107. package/dist/commands/review-commit-msg.js.map +1 -0
  108. package/dist/commands/review-commit-msg.test.d.ts +11 -0
  109. package/dist/commands/review-commit-msg.test.d.ts.map +1 -0
  110. package/dist/commands/review-commit-msg.test.js +126 -0
  111. package/dist/commands/review-commit-msg.test.js.map +1 -0
  112. package/dist/commands/review.d.ts +7 -0
  113. package/dist/commands/review.d.ts.map +1 -1
  114. package/dist/commands/review.js +138 -103
  115. package/dist/commands/review.js.map +1 -1
  116. package/dist/commands/review.test.js +267 -1
  117. package/dist/commands/review.test.js.map +1 -1
  118. package/dist/commands/status.d.ts.map +1 -1
  119. package/dist/commands/status.js +12 -11
  120. package/dist/commands/status.js.map +1 -1
  121. package/dist/commands/status.test.d.ts +8 -0
  122. package/dist/commands/status.test.d.ts.map +1 -0
  123. package/dist/commands/status.test.js +106 -0
  124. package/dist/commands/status.test.js.map +1 -0
  125. package/dist/index.d.ts +5 -4
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +48 -16
  128. package/dist/index.js.map +1 -1
  129. package/dist/lib/config.d.ts +1 -0
  130. package/dist/lib/config.d.ts.map +1 -1
  131. package/dist/lib/config.js +1 -1
  132. package/dist/lib/config.js.map +1 -1
  133. package/dist/lib/config.test.d.ts +9 -0
  134. package/dist/lib/config.test.d.ts.map +1 -0
  135. package/dist/lib/config.test.js +181 -0
  136. package/dist/lib/config.test.js.map +1 -0
  137. package/dist/lib/git-hooks.d.ts +19 -0
  138. package/dist/lib/git-hooks.d.ts.map +1 -0
  139. package/dist/lib/git-hooks.js +129 -0
  140. package/dist/lib/git-hooks.js.map +1 -0
  141. package/dist/lib/git-hooks.test.d.ts +11 -0
  142. package/dist/lib/git-hooks.test.d.ts.map +1 -0
  143. package/dist/lib/git-hooks.test.js +178 -0
  144. package/dist/lib/git-hooks.test.js.map +1 -0
  145. package/dist/lib/git.d.ts +33 -0
  146. package/dist/lib/git.d.ts.map +1 -0
  147. package/dist/lib/git.js +85 -0
  148. package/dist/lib/git.js.map +1 -0
  149. package/dist/lib/git.test.d.ts +2 -0
  150. package/dist/lib/git.test.d.ts.map +1 -0
  151. package/dist/lib/git.test.js +65 -0
  152. package/dist/lib/git.test.js.map +1 -0
  153. package/dist/lib/hook-templates.d.ts +12 -0
  154. package/dist/lib/hook-templates.d.ts.map +1 -0
  155. package/dist/lib/hook-templates.js +52 -0
  156. package/dist/lib/hook-templates.js.map +1 -0
  157. package/dist/lib/hook-templates.test.d.ts +11 -0
  158. package/dist/lib/hook-templates.test.d.ts.map +1 -0
  159. package/dist/lib/hook-templates.test.js +76 -0
  160. package/dist/lib/hook-templates.test.js.map +1 -0
  161. package/dist/lib/hooks-types.d.ts +30 -0
  162. package/dist/lib/hooks-types.d.ts.map +1 -0
  163. package/dist/lib/hooks-types.js +9 -0
  164. package/dist/lib/hooks-types.js.map +1 -0
  165. package/dist/lib/oauth.test.d.ts +8 -0
  166. package/dist/lib/oauth.test.d.ts.map +1 -0
  167. package/dist/lib/oauth.test.js +212 -0
  168. package/dist/lib/oauth.test.js.map +1 -0
  169. package/dist/ui/__tests__/format.test.d.ts +7 -0
  170. package/dist/ui/__tests__/format.test.d.ts.map +1 -0
  171. package/dist/ui/__tests__/format.test.js +220 -0
  172. package/dist/ui/__tests__/format.test.js.map +1 -0
  173. package/dist/ui/__tests__/theme.test.d.ts +7 -0
  174. package/dist/ui/__tests__/theme.test.d.ts.map +1 -0
  175. package/dist/ui/__tests__/theme.test.js +79 -0
  176. package/dist/ui/__tests__/theme.test.js.map +1 -0
  177. package/dist/ui/__tests__/tui.test.d.ts +9 -0
  178. package/dist/ui/__tests__/tui.test.d.ts.map +1 -0
  179. package/dist/ui/__tests__/tui.test.js +222 -0
  180. package/dist/ui/__tests__/tui.test.js.map +1 -0
  181. package/dist/ui/format.d.ts +38 -0
  182. package/dist/ui/format.d.ts.map +1 -0
  183. package/dist/ui/format.js +136 -0
  184. package/dist/ui/format.js.map +1 -0
  185. package/dist/ui/theme.d.ts +26 -0
  186. package/dist/ui/theme.d.ts.map +1 -0
  187. package/dist/ui/theme.js +63 -0
  188. package/dist/ui/theme.js.map +1 -0
  189. package/dist/ui/tui.d.ts +44 -0
  190. package/dist/ui/tui.d.ts.map +1 -0
  191. package/dist/ui/tui.js +121 -0
  192. package/dist/ui/tui.js.map +1 -0
  193. package/package.json +4 -2
package/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  AI-powered code review from the command line. **Free** with GitHub Models.
4
4
 
5
5
  ```bash
6
- npx @ghagga/cli login
7
- npx @ghagga/cli review
6
+ npx ghagga login
7
+ npx ghagga review
8
8
  ```
9
9
 
10
10
  That's it. Zero config, zero cost.
@@ -24,7 +24,7 @@ GHAGGA is a multi-agent AI code reviewer that analyzes your code changes using L
24
24
  ### 1. Login (one time)
25
25
 
26
26
  ```bash
27
- npx @ghagga/cli login
27
+ npx ghagga login
28
28
  ```
29
29
 
30
30
  This authenticates with GitHub using Device Flow. Your GitHub token gives you **free access** to AI models via [GitHub Models](https://github.com/marketplace/models).
@@ -33,23 +33,39 @@ This authenticates with GitHub using Device Flow. Your GitHub token gives you **
33
33
 
34
34
  ```bash
35
35
  # Review staged/uncommitted changes (simple mode)
36
- npx @ghagga/cli review
36
+ npx ghagga review
37
37
 
38
38
  # Thorough review with 5 specialist agents
39
- npx @ghagga/cli review --mode workflow
39
+ npx ghagga review --mode workflow
40
40
 
41
41
  # Balanced review with for/against/neutral voting
42
- npx @ghagga/cli review --mode consensus
42
+ npx ghagga review --mode consensus
43
43
 
44
44
  # See detailed progress of each step
45
- npx @ghagga/cli review --mode workflow --verbose
45
+ npx ghagga review --mode workflow --verbose
46
46
  ```
47
47
 
48
48
  ### 3. Check your status
49
49
 
50
50
  ```bash
51
- npx @ghagga/cli status # Show auth & config
52
- npx @ghagga/cli logout # Clear credentials
51
+ npx ghagga status # Show auth & config
52
+ npx ghagga logout # Clear credentials
53
+ ```
54
+
55
+ ### 4. Install git hooks (optional)
56
+
57
+ ```bash
58
+ npx ghagga hooks install # Auto-review on every commit
59
+ npx ghagga hooks status # Check hook status
60
+ npx ghagga hooks uninstall # Remove hooks
61
+ ```
62
+
63
+ ### 5. Manage review memory
64
+
65
+ ```bash
66
+ npx ghagga memory list # List stored observations
67
+ npx ghagga memory search "error handling" # Search by content
68
+ npx ghagga memory stats # Database statistics
53
69
  ```
54
70
 
55
71
  ## Global Installation
@@ -57,21 +73,31 @@ npx @ghagga/cli logout # Clear credentials
57
73
  If you use it frequently:
58
74
 
59
75
  ```bash
60
- npm install -g @ghagga/cli
76
+ npm install -g ghagga
61
77
 
62
78
  ghagga login
63
79
  ghagga review
64
80
  ghagga review -m workflow -v
65
81
  ```
66
82
 
67
- ## Options
83
+ ## Global Options
84
+
85
+ ```
86
+ --plain Disable styled terminal output (auto-enabled in non-TTY/CI)
87
+ --version Show version number
88
+ --help Show help
89
+ ```
90
+
91
+ > The CLI uses [`@clack/prompts`](https://github.com/natemoo-re/clack) for styled terminal output (spinners, colored headers). In non-TTY or CI environments, output automatically falls back to plain `console.log`.
92
+
93
+ ## Review Options
68
94
 
69
95
  ```
70
96
  Usage: ghagga review [options] [path]
71
97
 
72
98
  Options:
73
99
  -m, --mode <mode> Review mode: simple, workflow, consensus (default: "simple")
74
- -p, --provider <provider> LLM provider: github, openai, anthropic, google
100
+ -p, --provider <provider> LLM provider: github, anthropic, openai, google, ollama, qwen
75
101
  --model <model> LLM model identifier
76
102
  --api-key <key> LLM provider API key
77
103
  -f, --format <format> Output format: markdown, json (default: "markdown")
@@ -79,9 +105,43 @@ Options:
79
105
  --no-semgrep Disable Semgrep static analysis
80
106
  --no-trivy Disable Trivy vulnerability scanning
81
107
  --no-cpd Disable CPD duplicate detection
108
+ --no-memory Disable review memory (skip search and persist)
109
+ --memory-backend <type> Memory backend: sqlite (default) or engram
110
+ --staged Review only staged files (for pre-commit hook)
111
+ --quick Static analysis only, skip AI review (~5-10s)
112
+ --commit-msg <file> Validate commit message from file
113
+ --exit-on-issues Exit with code 1 if critical/high issues found
82
114
  -c, --config <path> Path to .ghagga.json config file
83
115
  ```
84
116
 
117
+ ## Git Hooks
118
+
119
+ Install git hooks for automatic code review on every commit:
120
+
121
+ ```bash
122
+ ghagga hooks install # Install pre-commit + commit-msg hooks
123
+ ghagga hooks install --force # Overwrite existing hooks (backs up originals)
124
+ ghagga hooks install --pre-commit # Only pre-commit hook
125
+ ghagga hooks install --commit-msg # Only commit-msg hook
126
+ ghagga hooks uninstall # Remove GHAGGA-managed hooks
127
+ ghagga hooks status # Show hook status
128
+ ```
129
+
130
+ Hooks auto-detect `ghagga` in PATH and fail gracefully if not found. Installed hooks use `--plain --exit-on-issues` automatically.
131
+
132
+ ## Memory Subcommands
133
+
134
+ ```bash
135
+ ghagga memory list [--repo <owner/repo>] [--type <type>] [--limit <n>]
136
+ ghagga memory search [--repo <owner/repo>] [--limit <n>] <query>
137
+ ghagga memory show <id>
138
+ ghagga memory delete [--force] <id>
139
+ ghagga memory stats
140
+ ghagga memory clear [--repo <owner/repo>] [--force]
141
+ ```
142
+
143
+ Memory is stored locally at `~/.config/ghagga/memory.db` (SQLite + FTS5). Observations are automatically extracted from reviews and used to provide context in future reviews.
144
+
85
145
  ## BYOK (Bring Your Own Key)
86
146
 
87
147
  Use any supported LLM provider:
@@ -102,6 +162,9 @@ ghagga review --provider anthropic --api-key sk-ant-...
102
162
 
103
163
  # Google
104
164
  ghagga review --provider google --api-key AIza...
165
+
166
+ # Qwen (Alibaba Cloud)
167
+ ghagga review --provider qwen --api-key sk-...
105
168
  ```
106
169
 
107
170
  ## Local Models with Ollama
@@ -125,10 +188,13 @@ ghagga review --provider ollama
125
188
  ## Environment Variables
126
189
 
127
190
  ```bash
128
- GHAGGA_API_KEY=<key> # API key for the LLM provider
129
- GHAGGA_PROVIDER=<provider> # LLM provider override
130
- GHAGGA_MODEL=<model> # Model identifier override
131
- GITHUB_TOKEN=<token> # GitHub token (fallback for github provider)
191
+ GHAGGA_API_KEY=<key> # API key for the LLM provider
192
+ GHAGGA_PROVIDER=<provider> # LLM provider override
193
+ GHAGGA_MODEL=<model> # Model identifier override
194
+ GHAGGA_MEMORY_BACKEND=<type> # Memory backend: sqlite (default) or engram
195
+ GHAGGA_ENGRAM_HOST=<url> # Engram server URL (default: http://localhost:7437)
196
+ GHAGGA_ENGRAM_TIMEOUT=<seconds> # Engram connection timeout (default: 5)
197
+ GITHUB_TOKEN=<token> # GitHub token (fallback for github provider)
132
198
  ```
133
199
 
134
200
  ## Config File
@@ -148,11 +214,15 @@ Create a `.ghagga.json` in your project root:
148
214
 
149
215
  ## How It Works
150
216
 
151
- 1. Gets your `git diff` (staged or uncommitted changes)
217
+ 1. Gets your `git diff` (staged or uncommitted changes; `--staged` uses `git diff --cached`)
152
218
  2. Parses the diff and detects tech stacks
153
219
  3. Runs static analysis (Semgrep, Trivy, CPD) if available
154
- 4. Sends the diff + context to the AI review agent
155
- 5. Returns findings with severity, file, line, and suggestions
220
+ 4. Searches memory for relevant past observations (SQLite + FTS5 at `~/.config/ghagga/memory.db`, or Engram if `--memory-backend engram`)
221
+ 5. Sends the diff + static findings + memory context to the AI review agent (skipped with `--quick`)
222
+ 6. Returns findings with severity, file, line, and suggestions
223
+ 7. Persists new observations (decisions, patterns, bug fixes) to local memory for future reviews
224
+
225
+ With `ghagga hooks install`, steps 1-7 run automatically on every commit via pre-commit and commit-msg hooks.
156
226
 
157
227
  ## Requirements
158
228
 
@@ -167,5 +237,6 @@ MIT
167
237
  ## Links
168
238
 
169
239
  - [GitHub Repository](https://github.com/JNZader/ghagga)
240
+ - [Full CLI Guide](https://jnzader.github.io/ghagga/docs/#/cli) — Complete setup guide with troubleshooting
170
241
  - [Documentation](https://jnzader.github.io/ghagga/docs/)
171
242
  - [Landing Page](https://jnzader.github.io/ghagga/)
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Hooks command group barrel.
3
+ *
4
+ * Registers the `ghagga hooks` subcommands (install, uninstall, status)
5
+ * and exports the parent Command for registration in the CLI entry point.
6
+ */
7
+ import { Command } from 'commander';
8
+ export declare const hooksCommand: Command;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,YAAY,SACmC,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Hooks command group barrel.
3
+ *
4
+ * Registers the `ghagga hooks` subcommands (install, uninstall, status)
5
+ * and exports the parent Command for registration in the CLI entry point.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { registerInstallCommand } from './install.js';
9
+ import { registerUninstallCommand } from './uninstall.js';
10
+ import { registerStatusCommand } from './status.js';
11
+ export const hooksCommand = new Command('hooks')
12
+ .description('Manage git hooks for automated code review');
13
+ registerInstallCommand(hooksCommand);
14
+ registerUninstallCommand(hooksCommand);
15
+ registerStatusCommand(hooksCommand);
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,4CAA4C,CAAC,CAAC;AAE7D,sBAAsB,CAAC,YAAY,CAAC,CAAC;AACrC,wBAAwB,CAAC,YAAY,CAAC,CAAC;AACvC,qBAAqB,CAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `ghagga hooks install` subcommand.
3
+ *
4
+ * Installs GHAGGA-managed pre-commit and/or commit-msg hooks into the
5
+ * current git repository. Handles backup of existing non-GHAGGA hooks,
6
+ * idempotent reinstall of GHAGGA hooks, and the --force flag for
7
+ * overwriting external hooks.
8
+ *
9
+ * @see Phase 3, Task 3.2
10
+ */
11
+ import { Command } from 'commander';
12
+ export declare function registerInstallCommand(parent: Command): void;
13
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAwD5D"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `ghagga hooks install` subcommand.
3
+ *
4
+ * Installs GHAGGA-managed pre-commit and/or commit-msg hooks into the
5
+ * current git repository. Handles backup of existing non-GHAGGA hooks,
6
+ * idempotent reinstall of GHAGGA hooks, and the --force flag for
7
+ * overwriting external hooks.
8
+ *
9
+ * @see Phase 3, Task 3.2
10
+ */
11
+ import { isGitRepo, getHooksDir, installHook } from '../../lib/git-hooks.js';
12
+ import { generatePreCommitHook, generateCommitMsgHook } from '../../lib/hook-templates.js';
13
+ import * as tui from '../../ui/tui.js';
14
+ export function registerInstallCommand(parent) {
15
+ parent
16
+ .command('install')
17
+ .description('Install git hooks for automated code review')
18
+ .option('--force', 'Overwrite existing non-GHAGGA hooks (with backup)')
19
+ .option('--pre-commit', 'Install only the pre-commit hook')
20
+ .option('--commit-msg', 'Install only the commit-msg hook')
21
+ .action((opts) => {
22
+ if (!isGitRepo()) {
23
+ tui.log.error('Not a git repository. Run this command from inside a git repo.');
24
+ process.exit(1);
25
+ }
26
+ const hooksDir = getHooksDir();
27
+ const force = opts.force ?? false;
28
+ // Determine which hooks to install.
29
+ // If neither flag is set, install both. If one or both are set, install only those.
30
+ const installPreCommit = opts.preCommit || (!opts.preCommit && !opts.commitMsg);
31
+ const installCommitMsg = opts.commitMsg || (!opts.preCommit && !opts.commitMsg);
32
+ const hooks = [];
33
+ if (installPreCommit) {
34
+ hooks.push({
35
+ type: 'pre-commit',
36
+ content: generatePreCommitHook(),
37
+ });
38
+ }
39
+ if (installCommitMsg) {
40
+ hooks.push({
41
+ type: 'commit-msg',
42
+ content: generateCommitMsgHook(),
43
+ });
44
+ }
45
+ let installed = 0;
46
+ for (const hook of hooks) {
47
+ const result = installHook(hooksDir, hook.type, hook.content, force);
48
+ if (result.success) {
49
+ tui.log.success(result.message);
50
+ installed++;
51
+ }
52
+ else {
53
+ tui.log.error(result.message);
54
+ }
55
+ }
56
+ if (installed > 0) {
57
+ tui.log.info(`\nInstalled ${installed} hook(s) to ${hooksDir}`);
58
+ }
59
+ else {
60
+ tui.log.warn('No hooks were installed.');
61
+ }
62
+ });
63
+ }
64
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/commands/hooks/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAE3F,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAQvC,MAAM,UAAU,sBAAsB,CAAC,MAAe;IACpD,MAAM;SACH,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,6CAA6C,CAAC;SAC1D,MAAM,CAAC,SAAS,EAAE,mDAAmD,CAAC;SACtE,MAAM,CAAC,cAAc,EAAE,kCAAkC,CAAC;SAC1D,MAAM,CAAC,cAAc,EAAE,kCAAkC,CAAC;SAC1D,MAAM,CAAC,CAAC,IAAoB,EAAE,EAAE;QAC/B,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACjB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;QAElC,oCAAoC;QACpC,oFAAoF;QACpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAChF,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEhF,MAAM,KAAK,GAA+C,EAAE,CAAC;QAE7D,IAAI,gBAAgB,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,qBAAqB,EAAE;aACjC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,gBAAgB,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,qBAAqB,EAAE;aACjC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAErE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAChC,SAAS,EAAE,CAAC;YACd,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;YAClB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,SAAS,eAAe,QAAQ,EAAE,CAAC,CAAC;QAClE,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for `ghagga hooks install` subcommand.
3
+ *
4
+ * Mocks git-hooks utilities, hook-templates, and the TUI layer.
5
+ * Tests hook selection (both, --pre-commit, --commit-msg),
6
+ * --force flag, non-git-repo error, and success/failure messages.
7
+ *
8
+ * @see Phase 4, Test 4
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=install.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.test.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/install.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Tests for `ghagga hooks install` subcommand.
3
+ *
4
+ * Mocks git-hooks utilities, hook-templates, and the TUI layer.
5
+ * Tests hook selection (both, --pre-commit, --commit-msg),
6
+ * --force flag, non-git-repo error, and success/failure messages.
7
+ *
8
+ * @see Phase 4, Test 4
9
+ */
10
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
11
+ import { Command } from 'commander';
12
+ // ─── Mocks ──────────────────────────────────────────────────────
13
+ const { mockIsGitRepo, mockGetHooksDir, mockInstallHook, mockGenPreCommit, mockGenCommitMsg } = vi.hoisted(() => ({
14
+ mockIsGitRepo: vi.fn(),
15
+ mockGetHooksDir: vi.fn(),
16
+ mockInstallHook: vi.fn(),
17
+ mockGenPreCommit: vi.fn(),
18
+ mockGenCommitMsg: vi.fn(),
19
+ }));
20
+ vi.mock('../../lib/git-hooks.js', () => ({
21
+ isGitRepo: (...args) => mockIsGitRepo(...args),
22
+ getHooksDir: (...args) => mockGetHooksDir(...args),
23
+ installHook: (...args) => mockInstallHook(...args),
24
+ }));
25
+ vi.mock('../../lib/hook-templates.js', () => ({
26
+ generatePreCommitHook: (...args) => mockGenPreCommit(...args),
27
+ generateCommitMsgHook: (...args) => mockGenCommitMsg(...args),
28
+ }));
29
+ vi.mock('../../ui/tui.js', () => ({
30
+ log: {
31
+ success: vi.fn(),
32
+ error: vi.fn(),
33
+ info: vi.fn(),
34
+ warn: vi.fn(),
35
+ },
36
+ }));
37
+ import { registerInstallCommand } from './install.js';
38
+ import * as tui from '../../ui/tui.js';
39
+ // ─── Helpers ────────────────────────────────────────────────────
40
+ class ProcessExitError extends Error {
41
+ code;
42
+ constructor(code) {
43
+ super(`process.exit(${code})`);
44
+ this.code = code;
45
+ }
46
+ }
47
+ async function runInstallCommand(args = []) {
48
+ const parent = new Command('hooks');
49
+ registerInstallCommand(parent);
50
+ try {
51
+ await parent.parseAsync(['install', ...args], { from: 'user' });
52
+ }
53
+ catch (err) {
54
+ if (!(err instanceof ProcessExitError))
55
+ throw err;
56
+ }
57
+ }
58
+ // ─── Setup ──────────────────────────────────────────────────────
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ let exitSpy;
61
+ beforeEach(() => {
62
+ vi.clearAllMocks();
63
+ exitSpy = vi
64
+ .spyOn(process, 'exit')
65
+ .mockImplementation(((code) => {
66
+ throw new ProcessExitError(code);
67
+ }));
68
+ // Defaults: in a git repo, hooks dir exists
69
+ mockIsGitRepo.mockReturnValue(true);
70
+ mockGetHooksDir.mockReturnValue('/repo/.git/hooks');
71
+ mockGenPreCommit.mockReturnValue('pre-commit-content');
72
+ mockGenCommitMsg.mockReturnValue('commit-msg-content');
73
+ mockInstallHook.mockReturnValue({
74
+ type: 'pre-commit',
75
+ success: true,
76
+ message: 'Installed pre-commit hook',
77
+ });
78
+ });
79
+ afterEach(() => {
80
+ exitSpy.mockRestore();
81
+ });
82
+ // ─── Tests ──────────────────────────────────────────────────────
83
+ describe('ghagga hooks install', () => {
84
+ it('installs both hooks by default', async () => {
85
+ mockInstallHook
86
+ .mockReturnValueOnce({ type: 'pre-commit', success: true, message: 'Installed pre-commit hook' })
87
+ .mockReturnValueOnce({ type: 'commit-msg', success: true, message: 'Installed commit-msg hook' });
88
+ await runInstallCommand();
89
+ expect(mockInstallHook).toHaveBeenCalledTimes(2);
90
+ expect(mockInstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'pre-commit', 'pre-commit-content', false);
91
+ expect(mockInstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'commit-msg', 'commit-msg-content', false);
92
+ expect(tui.log.success).toHaveBeenCalledTimes(2);
93
+ });
94
+ it('installs only pre-commit when --pre-commit is passed', async () => {
95
+ mockInstallHook.mockReturnValue({
96
+ type: 'pre-commit', success: true, message: 'Installed pre-commit hook',
97
+ });
98
+ await runInstallCommand(['--pre-commit']);
99
+ expect(mockInstallHook).toHaveBeenCalledTimes(1);
100
+ expect(mockInstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'pre-commit', 'pre-commit-content', false);
101
+ });
102
+ it('installs only commit-msg when --commit-msg is passed', async () => {
103
+ mockInstallHook.mockReturnValue({
104
+ type: 'commit-msg', success: true, message: 'Installed commit-msg hook',
105
+ });
106
+ await runInstallCommand(['--commit-msg']);
107
+ expect(mockInstallHook).toHaveBeenCalledTimes(1);
108
+ expect(mockInstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'commit-msg', 'commit-msg-content', false);
109
+ });
110
+ it('passes force flag when --force is used', async () => {
111
+ mockInstallHook.mockReturnValue({
112
+ type: 'pre-commit', success: true, message: 'Installed',
113
+ });
114
+ await runInstallCommand(['--force', '--pre-commit']);
115
+ expect(mockInstallHook).toHaveBeenCalledWith('/repo/.git/hooks', 'pre-commit', 'pre-commit-content', true);
116
+ });
117
+ it('exits with error when not in a git repo', async () => {
118
+ mockIsGitRepo.mockReturnValue(false);
119
+ await runInstallCommand();
120
+ expect(exitSpy).toHaveBeenCalledWith(1);
121
+ expect(tui.log.error).toHaveBeenCalledWith(expect.stringContaining('Not a git repository'));
122
+ expect(mockInstallHook).not.toHaveBeenCalled();
123
+ });
124
+ it('shows error message when install fails', async () => {
125
+ mockInstallHook
126
+ .mockReturnValueOnce({
127
+ type: 'pre-commit',
128
+ success: false,
129
+ message: 'Hook pre-commit already exists (not managed by GHAGGA). Use --force to overwrite.',
130
+ })
131
+ .mockReturnValueOnce({
132
+ type: 'commit-msg',
133
+ success: true,
134
+ message: 'Installed commit-msg hook',
135
+ });
136
+ await runInstallCommand();
137
+ expect(tui.log.error).toHaveBeenCalledWith(expect.stringContaining('--force'));
138
+ expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('Installed commit-msg'));
139
+ });
140
+ it('shows "no hooks installed" warning when all installs fail', async () => {
141
+ mockInstallHook.mockReturnValue({
142
+ type: 'pre-commit',
143
+ success: false,
144
+ message: 'Failed',
145
+ });
146
+ await runInstallCommand();
147
+ expect(tui.log.warn).toHaveBeenCalledWith(expect.stringContaining('No hooks were installed'));
148
+ });
149
+ it('shows install count summary on success', async () => {
150
+ mockInstallHook
151
+ .mockReturnValueOnce({ type: 'pre-commit', success: true, message: 'ok' })
152
+ .mockReturnValueOnce({ type: 'commit-msg', success: true, message: 'ok' });
153
+ await runInstallCommand();
154
+ expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Installed 2 hook(s)'));
155
+ });
156
+ });
157
+ //# sourceMappingURL=install.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.test.js","sourceRoot":"","sources":["../../../src/commands/hooks/install.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,mEAAmE;AAEnE,MAAM,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,GAC3F,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAChB,aAAa,EAAE,EAAE,CAAC,EAAE,EAAE;IACtB,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE;IACxB,eAAe,EAAE,EAAE,CAAC,EAAE,EAAE;IACxB,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE;IACzB,gBAAgB,EAAE,EAAE,CAAC,EAAE,EAAE;CAC1B,CAAC,CAAC,CAAC;AAEN,EAAE,CAAC,IAAI,CAAC,wBAAwB,EAAE,GAAG,EAAE,CAAC,CAAC;IACvC,SAAS,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;IACzD,WAAW,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC;IAC7D,WAAW,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC;CAC9D,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,qBAAqB,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;IACxE,qBAAqB,EAAE,CAAC,GAAG,IAAe,EAAE,EAAE,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC;CACzE,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC;IAChC,GAAG,EAAE;QACH,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;QAChB,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;KACd;CACF,CAAC,CAAC,CAAC;AAEJ,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAEvC,mEAAmE;AAEnE,MAAM,gBAAiB,SAAQ,KAAK;IACf;IAAnB,YAAmB,IAAwB;QACzC,KAAK,CAAC,gBAAgB,IAAI,GAAG,CAAC,CAAC;QADd,SAAI,GAAJ,IAAI,CAAoB;IAE3C,CAAC;CACF;AAED,KAAK,UAAU,iBAAiB,CAAC,OAAiB,EAAE;IAClD,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,CAAC,GAAG,YAAY,gBAAgB,CAAC;YAAE,MAAM,GAAG,CAAC;IACpD,CAAC;AACH,CAAC;AAED,mEAAmE;AAEnE,8DAA8D;AAC9D,IAAI,OAAY,CAAC;AAEjB,UAAU,CAAC,GAAG,EAAE;IACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACnB,OAAO,GAAG,EAAE;SACT,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC;SACtB,kBAAkB,CAAC,CAAC,CAAC,IAAa,EAAE,EAAE;QACrC,MAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC,CAAU,CAAC,CAAC;IAEf,4CAA4C;IAC5C,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACpC,eAAe,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAAC;IACpD,gBAAgB,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;IACvD,gBAAgB,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;IACvD,eAAe,CAAC,eAAe,CAAC;QAC9B,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,2BAA2B;KACrC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,CAAC,WAAW,EAAE,CAAC;AACxB,CAAC,CAAC,CAAC;AAEH,mEAAmE;AAEnE,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,eAAe;aACZ,mBAAmB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC;aAChG,mBAAmB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC,CAAC;QAEpG,MAAM,iBAAiB,EAAE,CAAC;QAE1B,MAAM,CAAC,eAAe,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,KAAK,CAC9D,CAAC;QACF,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,KAAK,CAC9D,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,eAAe,CAAC,eAAe,CAAC;YAC9B,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,2BAA2B;SACxE,CAAC,CAAC;QAEH,MAAM,iBAAiB,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;QAE1C,MAAM,CAAC,eAAe,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,KAAK,CAC9D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,eAAe,CAAC,eAAe,CAAC;YAC9B,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,2BAA2B;SACxE,CAAC,CAAC;QAEH,MAAM,iBAAiB,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC;QAE1C,MAAM,CAAC,eAAe,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,KAAK,CAC9D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,eAAe,CAAC,eAAe,CAAC;YAC9B,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW;SACxD,CAAC,CAAC;QAEH,MAAM,iBAAiB,CAAC,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC,CAAC;QAErD,MAAM,CAAC,eAAe,CAAC,CAAC,oBAAoB,CAC1C,kBAAkB,EAAE,YAAY,EAAE,oBAAoB,EAAE,IAAI,CAC7D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,aAAa,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAErC,MAAM,iBAAiB,EAAE,CAAC;QAE1B,MAAM,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,oBAAoB,CACxC,MAAM,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAChD,CAAC;QACF,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,eAAe;aACZ,mBAAmB,CAAC;YACnB,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,mFAAmF;SAC7F,CAAC;aACD,mBAAmB,CAAC;YACnB,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,2BAA2B;SACrC,CAAC,CAAC;QAEL,MAAM,iBAAiB,EAAE,CAAC;QAE1B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,oBAAoB,CACxC,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CACnC,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAC1C,MAAM,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAChD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,eAAe,CAAC,eAAe,CAAC;YAC9B,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAC;QAEH,MAAM,iBAAiB,EAAE,CAAC;QAE1B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CACvC,MAAM,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CACnD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,eAAe;aACZ,mBAAmB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aACzE,mBAAmB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7E,MAAM,iBAAiB,EAAE,CAAC;QAE1B,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,oBAAoB,CACvC,MAAM,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAC/C,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `ghagga hooks status` subcommand.
3
+ *
4
+ * Shows the status of pre-commit and commit-msg hooks in the
5
+ * current git repository: not installed, installed (GHAGGA-managed),
6
+ * or installed (external).
7
+ *
8
+ * @see Phase 3, Task 3.4
9
+ */
10
+ import { Command } from 'commander';
11
+ export declare function registerStatusCommand(parent: Command): void;
12
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA0B3D"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `ghagga hooks status` subcommand.
3
+ *
4
+ * Shows the status of pre-commit and commit-msg hooks in the
5
+ * current git repository: not installed, installed (GHAGGA-managed),
6
+ * or installed (external).
7
+ *
8
+ * @see Phase 3, Task 3.4
9
+ */
10
+ import { isGitRepo, getHooksDir, getHookStatus } from '../../lib/git-hooks.js';
11
+ import * as tui from '../../ui/tui.js';
12
+ const HOOK_TYPES = ['pre-commit', 'commit-msg'];
13
+ export function registerStatusCommand(parent) {
14
+ parent
15
+ .command('status')
16
+ .description('Show status of git hooks')
17
+ .action(() => {
18
+ if (!isGitRepo()) {
19
+ tui.log.error('Not a git repository. Run this command from inside a git repo.');
20
+ process.exit(1);
21
+ }
22
+ const hooksDir = getHooksDir();
23
+ tui.log.info(`Hooks directory: ${hooksDir}\n`);
24
+ for (const hookType of HOOK_TYPES) {
25
+ const status = getHookStatus(hooksDir, hookType);
26
+ if (!status.installed) {
27
+ tui.log.info(` ${hookType}: not installed`);
28
+ }
29
+ else if (status.managedByGhagga) {
30
+ tui.log.success(` ${hookType}: installed (GHAGGA-managed)`);
31
+ }
32
+ else {
33
+ tui.log.warn(` ${hookType}: installed (external — not managed by GHAGGA)`);
34
+ }
35
+ }
36
+ });
37
+ }
38
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/commands/hooks/status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE/E,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAEvC,MAAM,UAAU,GAAe,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;AAE5D,MAAM,UAAU,qBAAqB,CAAC,MAAe;IACnD,MAAM;SACH,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,0BAA0B,CAAC;SACvC,MAAM,CAAC,GAAG,EAAE;QACX,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YACjB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,gEAAgE,CAAC,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAE/B,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,QAAQ,IAAI,CAAC,CAAC;QAE/C,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAEjD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;gBACtB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,QAAQ,iBAAiB,CAAC,CAAC;YAC/C,CAAC;iBAAM,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;gBAClC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,8BAA8B,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,QAAQ,gDAAgD,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for `ghagga hooks status` subcommand.
3
+ *
4
+ * Mocks git-hooks utilities and the TUI layer.
5
+ * Tests display of hook status for both hooks, non-git-repo error,
6
+ * and correct labels for not-installed / GHAGGA / external hooks.
7
+ *
8
+ * @see Phase 4, Test 6
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=status.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.test.d.ts","sourceRoot":"","sources":["../../../src/commands/hooks/status.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}