@goodfoot/claude-code-hooks 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -374,21 +374,27 @@ async function discoverHookFiles(pattern, cwd) {
374
374
  /**
375
375
  * Compiles a TypeScript hook file to a self-contained ESM executable.
376
376
  *
377
- * Creates a wrapper that imports the hook and calls execute(), then bundles
378
- * everything together including the runtime.
377
+ * Uses a two-step process to generate stable content hashes:
378
+ * 1. Compile WITHOUT sourcemaps → generate stable content hash
379
+ * 2. Compile WITH sourcemaps → final output content
380
+ *
381
+ * This ensures content hashes are reproducible across different build
382
+ * environments, since sourcemaps contain file paths that can vary.
383
+ *
379
384
  * @param options - Compilation options
380
- * @returns Compiled output content as a string
385
+ * @returns Compiled content and stable content hash
381
386
  */
382
387
  async function compileHook(options) {
383
388
  const { sourcePath, logFilePath } = options;
384
389
  // Create a temporary wrapper file that imports the hook and executes it
385
- // Use system temp directory with deterministic name based on all inputs that affect output
386
- // This ensures the same inputs always produce the same temp path, making builds deterministic
387
- const hashInputs = [sourcePath, logFilePath ?? ""].join(":");
390
+ // Use system temp directory with deterministic name based on hook basename (not full path)
391
+ // This ensures builds are reproducible across different environments/machines
392
+ const hashInputs = [path.basename(sourcePath), logFilePath ? "log" : ""].join(":");
388
393
  const buildHash = crypto.createHash("sha256").update(hashInputs).digest("hex").substring(0, 16);
389
394
  const tempDir = path.join(os.tmpdir(), "claude-code-hooks-build", buildHash);
390
395
  const wrapperPath = path.join(tempDir, "wrapper.ts");
391
- const tempOutput = path.join(tempDir, "output.mjs");
396
+ const tempOutputNoSourcemap = path.join(tempDir, "output-no-sourcemap.mjs");
397
+ const tempOutputWithSourcemap = path.join(tempDir, "output.mjs");
392
398
  // Get the path to the runtime module (relative to this CLI)
393
399
  const runtimePath = path.resolve(path.dirname(new URL(import.meta.url).pathname), "./runtime.js");
394
400
  // Ensure temp directory exists (don't delete - concurrent builds may be using it)
@@ -405,14 +411,13 @@ import { execute } from '${runtimePath.replace(/\\/g, "/")}';
405
411
  execute(hook);
406
412
  `;
407
413
  fs.writeFileSync(wrapperPath, wrapperContent, "utf-8");
408
- await esbuild.build({
414
+ // Common esbuild options
415
+ const commonOptions = {
409
416
  entryPoints: [wrapperPath],
410
- outfile: tempOutput,
411
417
  format: "esm",
412
418
  platform: "node",
413
419
  target: "node20",
414
420
  bundle: true,
415
- sourcemap: "inline",
416
421
  minify: false,
417
422
  // Keep node built-ins external
418
423
  external: [
@@ -437,11 +442,25 @@ execute(hook);
437
442
  // Ensure we get clean ESM output
438
443
  mainFields: ["module", "main"],
439
444
  conditions: ["import", "node"],
445
+ };
446
+ // Step 1: Compile WITHOUT sourcemaps to generate stable content hash
447
+ await esbuild.build({
448
+ ...commonOptions,
449
+ outfile: tempOutputNoSourcemap,
450
+ sourcemap: false,
451
+ });
452
+ const contentForHash = fs.readFileSync(tempOutputNoSourcemap, "utf-8");
453
+ const contentHash = generateContentHash(contentForHash);
454
+ // Step 2: Compile WITH sourcemaps for final output
455
+ await esbuild.build({
456
+ ...commonOptions,
457
+ outfile: tempOutputWithSourcemap,
458
+ sourcemap: "inline",
440
459
  });
441
- // Read and return the compiled content
460
+ const content = fs.readFileSync(tempOutputWithSourcemap, "utf-8");
442
461
  // Don't delete temp directory - allows concurrent builds of same source
443
462
  // and the OS will clean up /tmp periodically
444
- return fs.readFileSync(tempOutput, "utf-8");
463
+ return { content, contentHash };
445
464
  }
446
465
  /**
447
466
  * Generates a content hash (SHA-256, 8-char prefix) for a compiled hook.
@@ -476,19 +495,17 @@ async function compileAllHooks(options) {
476
495
  matcher: metadata.matcher,
477
496
  timeout: metadata.timeout,
478
497
  });
479
- // Compile the hook
498
+ // Compile the hook (two-step process for stable content hash)
480
499
  log("info", `Compiling: ${sourcePath}`);
481
- const compiledContent = await compileHook({ sourcePath, outputDir, logFilePath });
482
- // Generate content hash
483
- const hash = generateContentHash(compiledContent);
484
- // Determine output filename
500
+ const { content, contentHash } = await compileHook({ sourcePath, outputDir, logFilePath });
501
+ // Determine output filename using the stable content hash
485
502
  const baseName = path.basename(sourcePath, path.extname(sourcePath));
486
- const outputFilename = `${baseName}.${hash}.mjs`;
503
+ const outputFilename = `${baseName}.${contentHash}.mjs`;
487
504
  const outputPath = path.join(outputDir, outputFilename);
488
505
  // Write compiled output with shebang for direct execution
489
506
  // --enable-source-maps enables stack traces with original source locations
490
507
  const shebang = "#!/usr/bin/env -S node --enable-source-maps\n";
491
- fs.writeFileSync(outputPath, shebang + compiledContent, { encoding: "utf-8", mode: 0o755 });
508
+ fs.writeFileSync(outputPath, shebang + content, { encoding: "utf-8", mode: 0o755 });
492
509
  log("info", `Wrote: ${outputPath}`);
493
510
  compiledHooks.push({
494
511
  sourcePath,
package/dist/scaffold.js CHANGED
@@ -117,7 +117,7 @@ function generatePackageJson(projectName, outputPath) {
117
117
  "@goodfoot/claude-code-hooks": "^1.0.9",
118
118
  },
119
119
  devDependencies: {
120
- "@biomejs/biome": "2.3.13",
120
+ "@biomejs/biome": "2.3.14",
121
121
  "@types/node": "^22.0.0",
122
122
  typescript: "^5.9.3",
123
123
  vitest: "^4.0.16",
@@ -160,7 +160,7 @@ function generateTsConfig() {
160
160
  */
161
161
  function generateBiomeConfig() {
162
162
  return `{
163
- "$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
163
+ "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
164
164
  "formatter": {
165
165
  "enabled": true,
166
166
  "indentStyle": "space",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodfoot/claude-code-hooks",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "Type-safe Claude Code hooks library with camelCase types and output builders",
5
5
  "homepage": "https://github.com/goodfoot-io/marketplace/tree/main/packages/claude-code-hooks",
6
6
  "repository": {
@@ -53,8 +53,8 @@
53
53
  "typescript": "^5.9.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@anthropic-ai/claude-agent-sdk": "^0.2.22",
57
- "@biomejs/biome": "2.3.13",
56
+ "@anthropic-ai/claude-agent-sdk": "^0.2.31",
57
+ "@biomejs/biome": "2.3.14",
58
58
  "@types/node": "^24",
59
59
  "ts-morph": "^25.0.0",
60
60
  "tsx": "^4.20.3",
package/types/cli.d.ts CHANGED
@@ -151,15 +151,29 @@ interface CompileHookOptions {
151
151
  /** Optional log file path to inject into compiled hook. */
152
152
  logFilePath?: string;
153
153
  }
154
+ /**
155
+ * Result of compiling a hook.
156
+ */
157
+ interface CompileHookResult {
158
+ /** Compiled content WITH sourcemaps (for final output). */
159
+ content: string;
160
+ /** Stable content hash generated from sourcemap-free output. */
161
+ contentHash: string;
162
+ }
154
163
  /**
155
164
  * Compiles a TypeScript hook file to a self-contained ESM executable.
156
165
  *
157
- * Creates a wrapper that imports the hook and calls execute(), then bundles
158
- * everything together including the runtime.
166
+ * Uses a two-step process to generate stable content hashes:
167
+ * 1. Compile WITHOUT sourcemaps → generate stable content hash
168
+ * 2. Compile WITH sourcemaps → final output content
169
+ *
170
+ * This ensures content hashes are reproducible across different build
171
+ * environments, since sourcemaps contain file paths that can vary.
172
+ *
159
173
  * @param options - Compilation options
160
- * @returns Compiled output content as a string
174
+ * @returns Compiled content and stable content hash
161
175
  */
162
- declare function compileHook(options: CompileHookOptions): Promise<string>;
176
+ declare function compileHook(options: CompileHookOptions): Promise<CompileHookResult>;
163
177
  /**
164
178
  * Generates a content hash (SHA-256, 8-char prefix) for a compiled hook.
165
179
  * @param content - Compiled hook content
package/types/types.d.ts CHANGED
@@ -307,13 +307,7 @@ export type SubagentStartInput = SDKSubagentStartHookInput;
307
307
  * ```
308
308
  * @see https://code.claude.com/docs/en/hooks#subagentstop
309
309
  */
310
- export interface SubagentStopInput extends SDKSubagentStopHookInput {
311
- /**
312
- * Type of subagent that is stopping.
313
- * Examples: 'explore', 'codebase-analysis', custom agent types
314
- */
315
- agent_type: string;
316
- }
310
+ export type SubagentStopInput = SDKSubagentStopHookInput;
317
311
  /**
318
312
  * Input for PreCompact hooks.
319
313
  *