@primitive.ai/prim 0.1.0-alpha.65 → 0.1.0-alpha.66

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.
@@ -616,6 +616,9 @@ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, resolve as re
616
616
  var PRIM_POST_COMMIT_BLOCK_START = "# >>> prim post-commit hook >>>";
617
617
  var PRIM_POST_COMMIT_BLOCK_END = "# <<< prim post-commit hook <<<";
618
618
  var PRIM_CREATED_MARK = "prim-created-post-commit-hook";
619
+ var PRIM_POST_REWRITE_BLOCK_START = "# >>> prim post-rewrite hook >>>";
620
+ var PRIM_POST_REWRITE_BLOCK_END = "# <<< prim post-rewrite hook <<<";
621
+ var PRIM_POST_REWRITE_CREATED_MARK = "prim-created-post-rewrite-hook";
619
622
  var LEGACY_PRIM_CREATED_MARK = "prim-created-hook";
620
623
  var LEGACY_PRIM_OWNED_HEADER = "# prim post-commit hook \u2014 installed by: prim hooks install (prim-managed-hook)";
621
624
  var LEGACY_GLOBAL_OWNED_HEADER = "# prim global post-commit hook (core.hooksPath) \u2014 managed by prim; do not edit.";
@@ -684,6 +687,67 @@ ${PRIM_POST_COMMIT_BLOCK_END}`;
684
687
  function postCommitHookBlock() {
685
688
  return currentPostCommitBlock();
686
689
  }
690
+ function postRewriteHookBlock() {
691
+ return `${PRIM_POST_REWRITE_BLOCK_START}
692
+ if [ "$(git config --get prim.active 2>/dev/null)" = "true" ]; then
693
+ case "$1" in amend|rebase) prim_rewrite_source="$1" ;; *) prim_rewrite_source= ;; esac
694
+ if [ -n "$prim_rewrite_source" ]; then
695
+ prim_rewrite_pairs_file=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || prim_rewrite_pairs_file=
696
+ if [ -n "$prim_rewrite_pairs_file" ] && ! chmod 600 "$prim_rewrite_pairs_file" 2>/dev/null; then
697
+ rm -f "$prim_rewrite_pairs_file"
698
+ prim_rewrite_pairs_file=
699
+ fi
700
+ if [ -n "$prim_rewrite_pairs_file" ]; then
701
+ if cat > "$prim_rewrite_pairs_file"; then
702
+ exec < "$prim_rewrite_pairs_file"
703
+ prim_rewrite_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_rewrite_branch=
704
+ prim_cache_dir="\${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin"
705
+ prim_post_rewrite_ran=0
706
+ if [ "\${PRIM_BIN_CACHE:-1}" != "0" ] && [ -f "$prim_cache_dir/prim-post-rewrite" ] && [ -f "$prim_cache_dir/node" ] && [ -n "$(find "$prim_cache_dir/prim-post-rewrite" -mmin "-\${PRIM_BIN_CACHE_TTL_MIN:-1440}" 2>/dev/null)" ]; then
707
+ prim_node=$(cat "$prim_cache_dir/node")
708
+ prim_entry=$(cat "$prim_cache_dir/prim-post-rewrite")
709
+ if [ -x "$prim_node" ] && [ -f "$prim_entry" ]; then
710
+ ( trap '' HUP; trap 'if [ -n "$prim_rewrite_pairs_file" ]; then rm -f "$prim_rewrite_pairs_file"; fi' 0; export PRIM_REWRITE_SOURCE="$prim_rewrite_source" PRIM_REWRITE_BRANCH="$prim_rewrite_branch" PRIM_REWRITE_PAIRS_FILE="$prim_rewrite_pairs_file"; "$prim_node" "$prim_entry" ) </dev/null >/dev/null 2>&1 &
711
+ prim_post_rewrite_ran=1
712
+ fi
713
+ fi
714
+ if [ "$prim_post_rewrite_ran" -eq 0 ]; then
715
+ if [ -x "./node_modules/.bin/prim-post-rewrite" ]; then
716
+ ( trap '' HUP; trap 'if [ -n "$prim_rewrite_pairs_file" ]; then rm -f "$prim_rewrite_pairs_file"; fi' 0; export PRIM_REWRITE_SOURCE="$prim_rewrite_source" PRIM_REWRITE_BRANCH="$prim_rewrite_branch" PRIM_REWRITE_PAIRS_FILE="$prim_rewrite_pairs_file"; ./node_modules/.bin/prim-post-rewrite ) </dev/null >/dev/null 2>&1 &
717
+ prim_post_rewrite_ran=1
718
+ elif command -v npx >/dev/null 2>&1; then
719
+ ( trap '' HUP; trap 'if [ -n "$prim_rewrite_pairs_file" ]; then rm -f "$prim_rewrite_pairs_file"; fi' 0; export PRIM_REWRITE_SOURCE="$prim_rewrite_source" PRIM_REWRITE_BRANCH="$prim_rewrite_branch" PRIM_REWRITE_PAIRS_FILE="$prim_rewrite_pairs_file"; npx --yes -p @primitive.ai/prim@latest prim-post-rewrite ) </dev/null >/dev/null 2>&1 &
720
+ prim_post_rewrite_ran=1
721
+ fi
722
+ fi
723
+ if [ "$prim_post_rewrite_ran" -eq 0 ]; then
724
+ rm -f "$prim_rewrite_pairs_file"
725
+ fi
726
+ unset prim_cache_dir prim_post_rewrite_ran prim_node prim_entry prim_rewrite_branch
727
+ else
728
+ rm -f "$prim_rewrite_pairs_file"
729
+ fi
730
+ unset prim_rewrite_pairs_file
731
+ fi
732
+ fi
733
+ unset prim_rewrite_source
734
+ fi
735
+ ${PRIM_POST_REWRITE_BLOCK_END}`;
736
+ }
737
+ var POST_COMMIT_MANAGED_HOOK = {
738
+ hookName: "post-commit",
739
+ blockStart: PRIM_POST_COMMIT_BLOCK_START,
740
+ blockEnd: PRIM_POST_COMMIT_BLOCK_END,
741
+ createdMark: PRIM_CREATED_MARK,
742
+ block: postCommitHookBlock
743
+ };
744
+ var POST_REWRITE_MANAGED_HOOK = {
745
+ hookName: "post-rewrite",
746
+ blockStart: PRIM_POST_REWRITE_BLOCK_START,
747
+ blockEnd: PRIM_POST_REWRITE_BLOCK_END,
748
+ createdMark: PRIM_POST_REWRITE_CREATED_MARK,
749
+ block: postRewriteHookBlock
750
+ };
687
751
  function legacyFloatingInvocation() {
688
752
  return `if command -v prim-post-commit >/dev/null 2>&1; then
689
753
  prim-post-commit || true
@@ -739,10 +803,10 @@ function isKnownLegacyGlobalGate(value) {
739
803
  const suffix = Buffer.from("\nfi\n");
740
804
  return value.subarray(0, prefix.length).equals(prefix) && value.subarray(-suffix.length).equals(suffix) && isPinnedInvocation(value.subarray(prefix.length, -suffix.length).toString("utf8"));
741
805
  }
742
- function createdScaffold() {
806
+ function createdScaffold(spec) {
743
807
  return Buffer.from(`#!/bin/sh
744
- ${currentPostCommitBlock()}
745
- # ${PRIM_CREATED_MARK}
808
+ ${spec.block()}
809
+ # ${spec.createdMark}
746
810
  `);
747
811
  }
748
812
  function safeGitPath(root, value) {
@@ -751,7 +815,7 @@ function safeGitPath(root, value) {
751
815
  }
752
816
  return isAbsolute2(value) ? resolve3(value) : resolve3(root, value);
753
817
  }
754
- function resolveEffectivePostCommitHook(cwd = process.cwd()) {
818
+ function resolveEffectiveManagedHook(spec, cwd = process.cwd()) {
755
819
  const gitRoot = gitToplevel(cwd);
756
820
  if (!gitRoot) throw new Error("not a git repository");
757
821
  const value = execFileSync("git", ["rev-parse", "--git-path", "hooks"], {
@@ -765,42 +829,49 @@ function resolveEffectivePostCommitHook(cwd = process.cwd()) {
765
829
  return {
766
830
  gitRoot,
767
831
  hooksDir,
768
- hookPath: resolve3(dirname3(hooksDir), "post-commit"),
832
+ hookPath: resolve3(dirname3(hooksDir), spec.hookName),
769
833
  kind: "husky_v9",
770
- dispatcherPath: resolve3(hooksDir, "post-commit")
834
+ dispatcherPath: resolve3(hooksDir, spec.hookName)
771
835
  };
772
836
  }
773
837
  return {
774
838
  gitRoot,
775
839
  hooksDir,
776
- hookPath: resolve3(hooksDir, "post-commit"),
840
+ hookPath: resolve3(hooksDir, spec.hookName),
777
841
  kind: "direct"
778
842
  };
779
843
  }
780
- function assertSafeFile(path) {
844
+ function resolveEffectivePostCommitHook(cwd = process.cwd()) {
845
+ return resolveEffectiveManagedHook(POST_COMMIT_MANAGED_HOOK, cwd);
846
+ }
847
+ function resolveEffectivePostRewriteHook(cwd = process.cwd()) {
848
+ return resolveEffectiveManagedHook(POST_REWRITE_MANAGED_HOOK, cwd);
849
+ }
850
+ function assertSafeFile(path, spec) {
781
851
  if (!existsSync3(path)) return void 0;
782
852
  const stat = lstatSync3(path);
783
853
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_HOOK_BYTES) {
784
- throw new Error(`unsafe post-commit hook target: ${path}`);
854
+ throw new Error(`unsafe ${spec.hookName} hook target: ${path}`);
785
855
  }
786
856
  return stat;
787
857
  }
788
- function assertUsableHuskyDispatcher(target) {
858
+ function assertUsableHuskyDispatcher(target, spec) {
789
859
  if (!target.dispatcherPath) return;
790
- const stat = assertSafeFile(target.dispatcherPath);
860
+ const stat = assertSafeFile(target.dispatcherPath, spec);
791
861
  if (!stat || (stat.mode & 64) === 0) {
792
862
  throw new Error(
793
- `Husky post-commit dispatcher is missing or not executable: ${target.dispatcherPath}`
863
+ `Husky ${spec.hookName} dispatcher is missing or not executable: ${target.dispatcherPath}`
794
864
  );
795
865
  }
796
- const dispatcher = decodeHookText(readHookFile(target.dispatcherPath));
797
- if (dispatcher === HUSKY_DIRECT_DISPATCHER) return;
866
+ const dispatcher = decodeHookText(readHookFile(target.dispatcherPath), spec);
867
+ const directDispatcher = HUSKY_DIRECT_DISPATCHER.replaceAll("post-commit", spec.hookName);
868
+ if (dispatcher === directDispatcher) return;
798
869
  const normalizedDispatcher = dispatcher.endsWith("\n") ? dispatcher.slice(0, -1) : dispatcher;
799
870
  if (!HUSKY_V9_DISPATCHERS.has(normalizedDispatcher)) {
800
- throw new Error(`unrecognized Husky post-commit dispatcher: ${target.dispatcherPath}`);
871
+ throw new Error(`unrecognized Husky ${spec.hookName} dispatcher: ${target.dispatcherPath}`);
801
872
  }
802
873
  const huskyRuntime = resolve3(dirname3(target.dispatcherPath), "h");
803
- const runtimeStat = assertSafeFile(huskyRuntime);
874
+ const runtimeStat = assertSafeFile(huskyRuntime, spec);
804
875
  const runtime = runtimeStat ? readHookFile(huskyRuntime) : void 0;
805
876
  const runtimeHash = runtime ? createHash2("sha256").update(runtime).digest("hex") : void 0;
806
877
  if (!runtimeHash || !HUSKY_V9_RUNTIME_HASHES.has(runtimeHash)) {
@@ -811,28 +882,28 @@ function readHookFile(path) {
811
882
  const value = readFileSync3(path);
812
883
  return Buffer.isBuffer(value) ? value : Buffer.from(value);
813
884
  }
814
- function decodeHookText(content) {
815
- if (content.includes(0)) throw new Error("binary post-commit hook");
885
+ function decodeHookText(content, spec) {
886
+ if (content.includes(0)) throw new Error(`binary ${spec.hookName} hook`);
816
887
  try {
817
888
  return new TextDecoder("utf-8", { fatal: true }).decode(content);
818
889
  } catch {
819
- throw new Error("binary post-commit hook");
890
+ throw new Error(`binary ${spec.hookName} hook`);
820
891
  }
821
892
  }
822
- function supportedShebangEnd(content) {
823
- const text = decodeHookText(content);
893
+ function supportedShebangEnd(content, spec) {
894
+ const text = decodeHookText(content, spec);
824
895
  const newline = text.indexOf("\n");
825
896
  const shebang = newline === -1 ? text : text.slice(0, newline);
826
897
  if (!SUPPORTED_SHEBANGS.has(shebang) || newline === -1) {
827
- throw new Error("unsupported post-commit hook interpreter");
898
+ throw new Error(`unsupported ${spec.hookName} hook interpreter`);
828
899
  }
829
900
  return Buffer.byteLength(text.slice(0, newline + 1), "utf8");
830
901
  }
831
- function shellInsertionPoint(content, allowShebangless) {
832
- const text = decodeHookText(content);
833
- if (text.startsWith("#!")) return supportedShebangEnd(content);
902
+ function shellInsertionPoint(content, allowShebangless, spec) {
903
+ const text = decodeHookText(content, spec);
904
+ if (text.startsWith("#!")) return supportedShebangEnd(content, spec);
834
905
  if (allowShebangless) return 0;
835
- throw new Error("unsupported post-commit hook interpreter");
906
+ throw new Error(`unsupported ${spec.hookName} hook interpreter`);
836
907
  }
837
908
  function allIndexes(haystack, needle) {
838
909
  const indexes = [];
@@ -845,28 +916,28 @@ function allIndexes(haystack, needle) {
845
916
  }
846
917
  return indexes;
847
918
  }
848
- function blockRange(content) {
849
- decodeHookText(content);
850
- const startMarker = Buffer.from(PRIM_POST_COMMIT_BLOCK_START);
851
- const endMarker = Buffer.from(PRIM_POST_COMMIT_BLOCK_END);
919
+ function blockRange(content, spec) {
920
+ decodeHookText(content, spec);
921
+ const startMarker = Buffer.from(spec.blockStart);
922
+ const endMarker = Buffer.from(spec.blockEnd);
852
923
  const starts = allIndexes(content, startMarker);
853
924
  const ends = allIndexes(content, endMarker);
854
925
  if (starts.length === 0 && ends.length === 0) return { kind: "absent" };
855
926
  if (starts.length !== 1 || ends.length !== 1 || ends[0] < starts[0]) {
856
- throw new Error("malformed Prim post-commit markers");
927
+ throw new Error(`malformed Prim ${spec.hookName} markers`);
857
928
  }
858
929
  const start = starts[0];
859
930
  const end = ends[0] + endMarker.length;
860
- const current = Buffer.from(currentPostCommitBlock());
931
+ const current = Buffer.from(spec.block());
861
932
  return content.subarray(start, end).equals(current) ? { kind: "current", start, end } : { kind: "stale", start, end };
862
933
  }
863
- function mergedContent(existing, allowShebangless) {
864
- const block = Buffer.from(currentPostCommitBlock());
934
+ function mergedContent(existing, allowShebangless, spec) {
935
+ const block = Buffer.from(spec.block());
865
936
  if (!existing) {
866
- return createdScaffold();
937
+ return createdScaffold(spec);
867
938
  }
868
- const range = blockRange(existing);
869
- if (range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
939
+ const range = blockRange(existing, spec);
940
+ if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
870
941
  ${LEGACY_GLOBAL_OWNED_HEADER}
871
942
  `)) {
872
943
  const gateAt = existing.indexOf(Buffer.from('if [ "$(git config --get prim.active'));
@@ -881,7 +952,7 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
881
952
  existing.subarray(0, gateAt),
882
953
  existing.subarray(chainAt)
883
954
  ]);
884
- const shebangEnd2 = shellInsertionPoint(withoutLegacyGate, allowShebangless);
955
+ const shebangEnd2 = shellInsertionPoint(withoutLegacyGate, allowShebangless, spec);
885
956
  return Buffer.concat([
886
957
  withoutLegacyGate.subarray(0, shebangEnd2),
887
958
  block,
@@ -889,25 +960,25 @@ ${LEGACY_GLOBAL_OWNED_HEADER}
889
960
  withoutLegacyGate.subarray(shebangEnd2)
890
961
  ]);
891
962
  }
892
- if (range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
963
+ if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && range.kind === "absent" && existing.toString("utf8").startsWith(`#!/bin/sh
893
964
  ${LEGACY_PRIM_OWNED_HEADER}
894
965
  `)) {
895
966
  const prefixLength = legacyProjectPrefixLength(existing);
896
967
  if (prefixLength === void 0) {
897
968
  throw new Error("unrecognized legacy Prim post-commit invocation");
898
969
  }
899
- return Buffer.concat([createdScaffold(), existing.subarray(prefixLength)]);
970
+ return Buffer.concat([createdScaffold(spec), existing.subarray(prefixLength)]);
900
971
  }
901
972
  if (range.kind !== "absent") {
902
973
  const oldCreatedPrefixes = [
903
974
  Buffer.from(`#!/bin/sh
904
- # ${PRIM_CREATED_MARK}
975
+ # ${spec.createdMark}
905
976
 
906
977
  `),
907
- Buffer.from(`#!/bin/sh
978
+ ...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
908
979
  # ${LEGACY_PRIM_CREATED_MARK}
909
980
 
910
- `)
981
+ `)] : []
911
982
  ];
912
983
  const oldCreated = oldCreatedPrefixes.some(
913
984
  (prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
@@ -915,10 +986,10 @@ ${LEGACY_PRIM_OWNED_HEADER}
915
986
  if (oldCreated) {
916
987
  const suffix = existing.subarray(range.end);
917
988
  const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
918
- return Buffer.concat([createdScaffold(), tail]);
989
+ return Buffer.concat([createdScaffold(spec), tail]);
919
990
  }
920
991
  }
921
- const shebangEnd = shellInsertionPoint(existing, allowShebangless);
992
+ const shebangEnd = shellInsertionPoint(existing, allowShebangless, spec);
922
993
  if (range.kind === "current" && range.start === shebangEnd) return existing;
923
994
  let withoutBlock = existing;
924
995
  if (range.kind !== "absent") {
@@ -938,22 +1009,22 @@ ${LEGACY_PRIM_OWNED_HEADER}
938
1009
  function errorCode(error) {
939
1010
  return typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
940
1011
  }
941
- function assertTargetUnchanged(path, expected, stat) {
1012
+ function assertTargetUnchanged(path, expected, spec, stat) {
942
1013
  let current;
943
1014
  try {
944
1015
  current = lstatSync3(path);
945
1016
  } catch (error) {
946
1017
  if (expected === void 0 && errorCode(error) === "ENOENT") return;
947
- throw new Error(`post-commit hook changed concurrently: ${path}`);
1018
+ throw new Error(`${spec.hookName} hook changed concurrently: ${path}`);
948
1019
  }
949
1020
  if (expected === void 0) {
950
- throw new Error(`post-commit hook appeared concurrently: ${path}`);
1021
+ throw new Error(`${spec.hookName} hook appeared concurrently: ${path}`);
951
1022
  }
952
1023
  if (!current.isFile() || current.isSymbolicLink() || current.size > MAX_HOOK_BYTES || current.dev !== stat?.dev || current.ino !== stat?.ino || (current.mode & 4095) !== ((stat?.mode ?? 0) & 4095) || !readHookFile(path).equals(expected)) {
953
- throw new Error(`post-commit hook changed concurrently: ${path}`);
1024
+ throw new Error(`${spec.hookName} hook changed concurrently: ${path}`);
954
1025
  }
955
1026
  }
956
- function rewriteHookAtomically(path, next, expected, stat) {
1027
+ function rewriteHookAtomically(path, next, expected, spec, stat) {
957
1028
  const parent = dirname3(path);
958
1029
  mkdirSync2(parent, { recursive: true });
959
1030
  const temporary = resolve3(
@@ -972,7 +1043,7 @@ function rewriteHookAtomically(path, next, expected, stat) {
972
1043
  fsyncSync2(fd);
973
1044
  closeSync3(fd);
974
1045
  fd = void 0;
975
- assertTargetUnchanged(path, expected, stat);
1046
+ assertTargetUnchanged(path, expected, spec, stat);
976
1047
  renameSync2(temporary, path);
977
1048
  } finally {
978
1049
  if (fd !== void 0) {
@@ -989,32 +1060,36 @@ function rewriteHookAtomically(path, next, expected, stat) {
989
1060
  }
990
1061
  }
991
1062
  }
992
- function unlinkHookUnchanged(path, expected, stat) {
993
- assertTargetUnchanged(path, expected, stat);
1063
+ function unlinkHookUnchanged(path, expected, stat, spec) {
1064
+ assertTargetUnchanged(path, expected, spec, stat);
994
1065
  unlinkSync(path);
995
1066
  }
996
1067
  function ensureEffectivePostCommitHook(cwd = process.cwd()) {
997
1068
  const target = resolveEffectivePostCommitHook(cwd);
998
- return ensureTarget(target);
1069
+ return ensureTarget(target, POST_COMMIT_MANAGED_HOOK);
1070
+ }
1071
+ function ensureEffectivePostRewriteHook(cwd = process.cwd()) {
1072
+ const target = resolveEffectivePostRewriteHook(cwd);
1073
+ return ensureTarget(target, POST_REWRITE_MANAGED_HOOK);
999
1074
  }
1000
- function ensureTarget(target, initialContent) {
1001
- assertUsableHuskyDispatcher(target);
1002
- const stat = assertSafeFile(target.hookPath);
1075
+ function ensureTarget(target, spec, initialContent) {
1076
+ assertUsableHuskyDispatcher(target, spec);
1077
+ const stat = assertSafeFile(target.hookPath, spec);
1003
1078
  if (stat && target.kind === "direct" && (stat.mode & 64) === 0) {
1004
- throw new Error(`existing post-commit hook is not executable: ${target.hookPath}`);
1079
+ throw new Error(`existing ${spec.hookName} hook is not executable: ${target.hookPath}`);
1005
1080
  }
1006
1081
  const existing = stat ? readHookFile(target.hookPath) : void 0;
1007
- const next = !existing && initialContent !== void 0 ? Buffer.from(initialContent) : mergedContent(existing, target.kind === "husky_v9");
1082
+ const next = !existing && initialContent !== void 0 ? Buffer.from(initialContent) : mergedContent(existing, target.kind === "husky_v9", spec);
1008
1083
  if (!existing && initialContent !== void 0) {
1009
- const shebangEnd = shellInsertionPoint(next, target.kind === "husky_v9");
1010
- const range = blockRange(next);
1084
+ const shebangEnd = shellInsertionPoint(next, target.kind === "husky_v9", spec);
1085
+ const range = blockRange(next, spec);
1011
1086
  if (range.kind !== "current" || range.start !== shebangEnd) {
1012
- throw new Error("invalid initial Prim post-commit scaffold");
1087
+ throw new Error(`invalid initial Prim ${spec.hookName} scaffold`);
1013
1088
  }
1014
1089
  }
1015
1090
  const contentChanged = !existing || !next.equals(existing);
1016
1091
  if (contentChanged) {
1017
- rewriteHookAtomically(target.hookPath, next, existing, stat);
1092
+ rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
1018
1093
  }
1019
1094
  return { path: target.hookPath, changed: contentChanged || !stat, kind: target.kind };
1020
1095
  }
@@ -1027,14 +1102,35 @@ function ensurePostCommitHookAtPath(hookPath, initialContent) {
1027
1102
  hookPath: absolute,
1028
1103
  kind: "direct"
1029
1104
  },
1105
+ POST_COMMIT_MANAGED_HOOK,
1106
+ initialContent
1107
+ );
1108
+ return { ...result, kind: "direct" };
1109
+ }
1110
+ function ensurePostRewriteHookAtPath(hookPath, initialContent) {
1111
+ const absolute = resolve3(hookPath);
1112
+ const result = ensureTarget(
1113
+ {
1114
+ gitRoot: dirname3(dirname3(absolute)),
1115
+ hooksDir: dirname3(absolute),
1116
+ hookPath: absolute,
1117
+ kind: "direct"
1118
+ },
1119
+ POST_REWRITE_MANAGED_HOOK,
1030
1120
  initialContent
1031
1121
  );
1032
1122
  return { ...result, kind: "direct" };
1033
1123
  }
1034
1124
  function inspectEffectivePostCommitHook(cwd = process.cwd()) {
1035
- const target = resolveEffectivePostCommitHook(cwd);
1125
+ return inspectEffectiveManagedHook(POST_COMMIT_MANAGED_HOOK, cwd);
1126
+ }
1127
+ function inspectEffectivePostRewriteHook(cwd = process.cwd()) {
1128
+ return inspectEffectiveManagedHook(POST_REWRITE_MANAGED_HOOK, cwd);
1129
+ }
1130
+ function inspectEffectiveManagedHook(spec, cwd) {
1131
+ const target = resolveEffectiveManagedHook(spec, cwd);
1036
1132
  try {
1037
- assertUsableHuskyDispatcher(target);
1133
+ assertUsableHuskyDispatcher(target, spec);
1038
1134
  } catch (error) {
1039
1135
  const missing = error instanceof Error && (error.message.includes("missing or not executable") || error.message.includes("unsafe"));
1040
1136
  return {
@@ -1047,7 +1143,7 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
1047
1143
  }
1048
1144
  let stat;
1049
1145
  try {
1050
- stat = assertSafeFile(target.hookPath);
1146
+ stat = assertSafeFile(target.hookPath, spec);
1051
1147
  } catch {
1052
1148
  return {
1053
1149
  ...target,
@@ -1069,8 +1165,8 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
1069
1165
  const executable = target.kind === "husky_v9" || (stat.mode & 64) !== 0;
1070
1166
  try {
1071
1167
  const content = readHookFile(target.hookPath);
1072
- const shebangEnd = shellInsertionPoint(content, target.kind === "husky_v9");
1073
- const range = blockRange(content);
1168
+ const shebangEnd = shellInsertionPoint(content, target.kind === "husky_v9", spec);
1169
+ const range = blockRange(content, spec);
1074
1170
  const positioned = range.kind !== "absent" && range.start === shebangEnd;
1075
1171
  const current = range.kind === "current" && positioned;
1076
1172
  return {
@@ -1094,7 +1190,11 @@ function inspectEffectivePostCommitHook(cwd = process.cwd()) {
1094
1190
  }
1095
1191
  function uninstallEffectivePostCommitHook(cwd = process.cwd()) {
1096
1192
  const target = resolveEffectivePostCommitHook(cwd);
1097
- return uninstallTarget(target);
1193
+ return uninstallTarget(target, POST_COMMIT_MANAGED_HOOK);
1194
+ }
1195
+ function uninstallEffectivePostRewriteHook(cwd = process.cwd()) {
1196
+ const target = resolveEffectivePostRewriteHook(cwd);
1197
+ return uninstallTarget(target, POST_REWRITE_MANAGED_HOOK);
1098
1198
  }
1099
1199
  function projectHooksPathIsConfigured(gitRoot) {
1100
1200
  for (const scope of ["--worktree", "--local"]) {
@@ -1127,13 +1227,29 @@ function uninstallProjectPostCommitHook(cwd = process.cwd()) {
1127
1227
  resolve3(safeGitPath(gitRoot, commonDir), "hooks", "post-commit")
1128
1228
  );
1129
1229
  }
1130
- function uninstallTarget(target) {
1131
- const stat = assertSafeFile(target.hookPath);
1230
+ function uninstallProjectPostRewriteHook(cwd = process.cwd()) {
1231
+ const gitRoot = gitToplevel(cwd);
1232
+ if (!gitRoot) throw new Error("not a git repository");
1233
+ if (projectHooksPathIsConfigured(gitRoot)) {
1234
+ return uninstallEffectivePostRewriteHook(gitRoot);
1235
+ }
1236
+ const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
1237
+ cwd: gitRoot,
1238
+ encoding: "utf8",
1239
+ stdio: ["ignore", "pipe", "ignore"],
1240
+ timeout: GIT_TIMEOUT_MS
1241
+ }).replace(/\r?\n$/u, "");
1242
+ return uninstallPostRewriteHookAtPath(
1243
+ resolve3(safeGitPath(gitRoot, commonDir), "hooks", POST_REWRITE_MANAGED_HOOK.hookName)
1244
+ );
1245
+ }
1246
+ function uninstallTarget(target, spec) {
1247
+ const stat = assertSafeFile(target.hookPath, spec);
1132
1248
  if (!stat) return { path: target.hookPath, changed: false, removedFile: false };
1133
1249
  const existing = readHookFile(target.hookPath);
1134
- const range = blockRange(existing);
1250
+ const range = blockRange(existing, spec);
1135
1251
  if (range.kind === "absent") {
1136
- if (existing.toString("utf8").startsWith(`#!/bin/sh
1252
+ if (spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName && existing.toString("utf8").startsWith(`#!/bin/sh
1137
1253
  ${LEGACY_PRIM_OWNED_HEADER}
1138
1254
  `)) {
1139
1255
  const prefixLength = legacyProjectPrefixLength(existing);
@@ -1142,47 +1258,49 @@ ${LEGACY_PRIM_OWNED_HEADER}
1142
1258
  }
1143
1259
  const tail = existing.subarray(prefixLength);
1144
1260
  if (tail.length === 0) {
1145
- unlinkHookUnchanged(target.hookPath, existing, stat);
1261
+ unlinkHookUnchanged(target.hookPath, existing, stat, spec);
1146
1262
  return { path: target.hookPath, changed: true, removedFile: true };
1147
1263
  }
1148
1264
  rewriteHookAtomically(
1149
1265
  target.hookPath,
1150
1266
  Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
1151
1267
  existing,
1268
+ spec,
1152
1269
  stat
1153
1270
  );
1154
1271
  return { path: target.hookPath, changed: true, removedFile: false };
1155
1272
  }
1156
1273
  return { path: target.hookPath, changed: false, removedFile: false };
1157
1274
  }
1158
- const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9");
1275
+ const shebangEnd = shellInsertionPoint(existing, target.kind === "husky_v9", spec);
1159
1276
  const suffix = existing.subarray(range.end);
1160
1277
  const createdSuffix = Buffer.from(`
1161
- # ${PRIM_CREATED_MARK}
1278
+ # ${spec.createdMark}
1162
1279
  `);
1163
1280
  if (range.start === shebangEnd && suffix.subarray(0, createdSuffix.length).equals(createdSuffix)) {
1164
1281
  const tail = suffix.subarray(createdSuffix.length);
1165
1282
  if (tail.length === 0) {
1166
- unlinkHookUnchanged(target.hookPath, existing, stat);
1283
+ unlinkHookUnchanged(target.hookPath, existing, stat, spec);
1167
1284
  return { path: target.hookPath, changed: true, removedFile: true };
1168
1285
  }
1169
1286
  rewriteHookAtomically(
1170
1287
  target.hookPath,
1171
1288
  Buffer.concat([existing.subarray(0, shebangEnd), tail]),
1172
1289
  existing,
1290
+ spec,
1173
1291
  stat
1174
1292
  );
1175
1293
  return { path: target.hookPath, changed: true, removedFile: false };
1176
1294
  }
1177
1295
  const createdPrefixes = [
1178
1296
  Buffer.from(`#!/bin/sh
1179
- # ${PRIM_CREATED_MARK}
1297
+ # ${spec.createdMark}
1180
1298
 
1181
1299
  `),
1182
- Buffer.from(`#!/bin/sh
1300
+ ...spec.hookName === POST_COMMIT_MANAGED_HOOK.hookName ? [Buffer.from(`#!/bin/sh
1183
1301
  # ${LEGACY_PRIM_CREATED_MARK}
1184
1302
 
1185
- `)
1303
+ `)] : []
1186
1304
  ];
1187
1305
  const createdPrefix = createdPrefixes.find(
1188
1306
  (prefix) => range.start === prefix.length && existing.subarray(0, prefix.length).equals(prefix)
@@ -1190,13 +1308,14 @@ ${LEGACY_PRIM_OWNED_HEADER}
1190
1308
  if (createdPrefix) {
1191
1309
  const tail = suffix.at(0) === 10 ? suffix.subarray(1) : suffix;
1192
1310
  if (tail.length === 0) {
1193
- unlinkHookUnchanged(target.hookPath, existing, stat);
1311
+ unlinkHookUnchanged(target.hookPath, existing, stat, spec);
1194
1312
  return { path: target.hookPath, changed: true, removedFile: true };
1195
1313
  }
1196
1314
  rewriteHookAtomically(
1197
1315
  target.hookPath,
1198
1316
  Buffer.concat([Buffer.from("#!/bin/sh\n"), tail]),
1199
1317
  existing,
1318
+ spec,
1200
1319
  stat
1201
1320
  );
1202
1321
  return { path: target.hookPath, changed: true, removedFile: false };
@@ -1205,17 +1324,32 @@ ${LEGACY_PRIM_OWNED_HEADER}
1205
1324
  existing.subarray(0, range.start),
1206
1325
  range.start === shebangEnd && suffix.at(0) === 10 ? suffix.subarray(1) : suffix
1207
1326
  ]);
1208
- rewriteHookAtomically(target.hookPath, next, existing, stat);
1327
+ rewriteHookAtomically(target.hookPath, next, existing, spec, stat);
1209
1328
  return { path: target.hookPath, changed: true, removedFile: false };
1210
1329
  }
1211
1330
  function uninstallPostCommitHookAtPath(hookPath) {
1212
1331
  const absolute = resolve3(hookPath);
1213
- return uninstallTarget({
1214
- gitRoot: dirname3(dirname3(absolute)),
1215
- hooksDir: dirname3(absolute),
1216
- hookPath: absolute,
1217
- kind: "direct"
1218
- });
1332
+ return uninstallTarget(
1333
+ {
1334
+ gitRoot: dirname3(dirname3(absolute)),
1335
+ hooksDir: dirname3(absolute),
1336
+ hookPath: absolute,
1337
+ kind: "direct"
1338
+ },
1339
+ POST_COMMIT_MANAGED_HOOK
1340
+ );
1341
+ }
1342
+ function uninstallPostRewriteHookAtPath(hookPath) {
1343
+ const absolute = resolve3(hookPath);
1344
+ return uninstallTarget(
1345
+ {
1346
+ gitRoot: dirname3(dirname3(absolute)),
1347
+ hooksDir: dirname3(absolute),
1348
+ hookPath: absolute,
1349
+ kind: "direct"
1350
+ },
1351
+ POST_REWRITE_MANAGED_HOOK
1352
+ );
1219
1353
  }
1220
1354
 
1221
1355
  // src/lib/repository-binding.ts
@@ -1254,11 +1388,17 @@ async function bindRepository(root, options) {
1254
1388
 
1255
1389
  export {
1256
1390
  postCommitHookBlock,
1391
+ postRewriteHookBlock,
1257
1392
  ensureEffectivePostCommitHook,
1393
+ ensureEffectivePostRewriteHook,
1258
1394
  ensurePostCommitHookAtPath,
1395
+ ensurePostRewriteHookAtPath,
1259
1396
  inspectEffectivePostCommitHook,
1397
+ inspectEffectivePostRewriteHook,
1260
1398
  uninstallProjectPostCommitHook,
1399
+ uninstallProjectPostRewriteHook,
1261
1400
  uninstallPostCommitHookAtPath,
1401
+ uninstallPostRewriteHookAtPath,
1262
1402
  resolveRepositoryBinding,
1263
1403
  bindRepository,
1264
1404
  printJson,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  commitAttributionFromEnvironment,
4
4
  toCommitMove
5
- } from "../chunk-5CKL5EYQ.js";
5
+ } from "../chunk-CPSE3Y3T.js";
6
6
  import {
7
7
  appendMove,
8
8
  resolveOrg