jettypod 4.4.104 → 4.4.105

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.
Binary file
package/jettypod.js CHANGED
@@ -1207,6 +1207,16 @@ const [,, command, ...args] = process.argv;
1207
1207
  // Run startup validation for commands that use the database
1208
1208
  // Skip for commands that don't need it (init, help, version)
1209
1209
  const commandsWithoutDb = ['init', 'help', undefined, '--help', '-h', '--version', '-v'];
1210
+ // Ensure git hooks are up-to-date (silent auto-update)
1211
+ if (fs.existsSync('.git')) {
1212
+ try {
1213
+ const { ensureHooksUpdated } = require('./lib/git-hooks');
1214
+ ensureHooksUpdated();
1215
+ } catch (err) {
1216
+ // Don't fail CLI if hook check fails
1217
+ }
1218
+ }
1219
+
1210
1220
  if (!commandsWithoutDb.includes(command) && fs.existsSync('.jettypod')) {
1211
1221
  try {
1212
1222
  const { validateOnStartup } = require('./lib/database');
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const crypto = require('crypto');
3
4
 
4
5
  /**
5
6
  * Install JettyPod git hooks into .git/hooks directory
@@ -88,8 +89,118 @@ function areHooksInstalled() {
88
89
  }
89
90
  }
90
91
 
92
+ /**
93
+ * Compute MD5 hash of content
94
+ * @param {string} content - Content to hash
95
+ * @returns {string} MD5 hash hex string
96
+ */
97
+ function hashContent(content) {
98
+ return crypto.createHash('md5').update(content).digest('hex');
99
+ }
100
+
101
+ /**
102
+ * Check if hooks need updating and reinstall if outdated
103
+ * Uses content hashing - any change to hook source triggers update
104
+ * Called on every CLI invocation to ensure hooks stay current
105
+ * @returns {boolean} True if hooks were updated, false if already current
106
+ */
107
+ function ensureHooksUpdated() {
108
+ const gitHooksDir = path.join(process.cwd(), '.git', 'hooks');
109
+ const jettypodHooksDir = path.join(__dirname);
110
+
111
+ if (!fs.existsSync(gitHooksDir)) {
112
+ return false; // Not a git repo
113
+ }
114
+
115
+ const hooks = ['pre-commit', 'post-commit', 'post-merge'];
116
+ let needsUpdate = false;
117
+
118
+ for (const hook of hooks) {
119
+ const sourcePath = path.join(jettypodHooksDir, hook);
120
+ const installedPath = path.join(gitHooksDir, hook);
121
+
122
+ if (!fs.existsSync(sourcePath)) {
123
+ continue; // Source hook doesn't exist
124
+ }
125
+
126
+ const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
127
+
128
+ // Check installed hook
129
+ if (!fs.existsSync(installedPath)) {
130
+ needsUpdate = true;
131
+ break;
132
+ }
133
+
134
+ const installedContent = fs.readFileSync(installedPath, 'utf-8');
135
+
136
+ // Compare hashes - any difference triggers update
137
+ if (hashContent(sourceContent) !== hashContent(installedContent)) {
138
+ needsUpdate = true;
139
+ break;
140
+ }
141
+ }
142
+
143
+ if (needsUpdate) {
144
+ // Silently reinstall - don't spam the user
145
+ try {
146
+ installHooksSilent();
147
+ return true;
148
+ } catch (err) {
149
+ // Don't fail the CLI command if hook update fails
150
+ return false;
151
+ }
152
+ }
153
+
154
+ return false;
155
+ }
156
+
157
+ /**
158
+ * Install hooks without console output (for auto-updates)
159
+ */
160
+ function installHooksSilent() {
161
+ const gitHooksDir = path.join(process.cwd(), '.git', 'hooks');
162
+ const jettypodHooksDir = path.join(__dirname);
163
+
164
+ if (!fs.existsSync(gitHooksDir)) {
165
+ return false;
166
+ }
167
+
168
+ const hooks = ['pre-commit', 'post-commit', 'post-merge'];
169
+
170
+ hooks.forEach(hook => {
171
+ let sourcePath = path.join(jettypodHooksDir, hook);
172
+ if (!fs.existsSync(sourcePath)) {
173
+ sourcePath = path.join(process.cwd(), '.jettypod', 'hooks', hook);
174
+ }
175
+
176
+ if (!fs.existsSync(sourcePath)) {
177
+ return;
178
+ }
179
+
180
+ const targetPath = path.join(gitHooksDir, hook);
181
+
182
+ let hookContent = fs.readFileSync(sourcePath, 'utf-8');
183
+ const jettypodRoot = path.resolve(__dirname, '..', '..');
184
+ const modulesPath = path.join(jettypodRoot, 'node_modules');
185
+
186
+ if (hookContent.includes('require(\'sqlite3\')')) {
187
+ hookContent = hookContent.replace(
188
+ '#!/usr/bin/env node',
189
+ `#!/usr/bin/env node\n\n// Injected module path\nprocess.env.NODE_PATH = '${modulesPath}' + ':' + (process.env.NODE_PATH || '');require('module').Module._initPaths();`
190
+ );
191
+ }
192
+
193
+ hookContent = hookContent.replace(/__JETTYPOD_ROOT__/g, jettypodRoot);
194
+
195
+ fs.writeFileSync(targetPath, hookContent);
196
+ fs.chmodSync(targetPath, 0o755);
197
+ });
198
+
199
+ return true;
200
+ }
201
+
91
202
  module.exports = {
92
203
  installHooks,
93
- areHooksInstalled
204
+ areHooksInstalled,
205
+ ensureHooksUpdated
94
206
  };
95
- // test
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.104",
3
+ "version": "4.4.105",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -69,5 +69,3 @@
69
69
  "node": ">=18"
70
70
  }
71
71
  }
72
-
73
-