@primitive.ai/prim 0.1.0-alpha.93 → 0.1.0-alpha.94

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.
@@ -52,9 +52,9 @@ var require_ucs2length = __commonJS({
52
52
  });
53
53
 
54
54
  // src/hooks/post-commit.ts
55
- import { execFileSync as execFileSync4, spawn } from "child_process";
56
- import { lstatSync as lstatSync3, readFileSync as readFileSync10 } from "fs";
57
- import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute4, join as join11 } from "path";
55
+ import { execFileSync as execFileSync5, spawn } from "child_process";
56
+ import { lstatSync as lstatSync3, readFileSync as readFileSync11 } from "fs";
57
+ import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join12 } from "path";
58
58
  import { fileURLToPath } from "url";
59
59
 
60
60
  // src/binding.ts
@@ -401,6 +401,69 @@ import {
401
401
  writeFileSync
402
402
  } from "fs";
403
403
  import { dirname as dirname2 } from "path";
404
+ var DEFAULT_FILE_MODE = 438;
405
+ function syncDirectory(path) {
406
+ const fd = openSync(path, constants.O_RDONLY);
407
+ try {
408
+ fsyncSync(fd);
409
+ } finally {
410
+ closeSync(fd);
411
+ }
412
+ }
413
+ function syncCreatedParentChain(firstCreatedParent, parent) {
414
+ const createdDirectories = [];
415
+ let current = parent;
416
+ while (current !== firstCreatedParent) {
417
+ createdDirectories.push(current);
418
+ const next = dirname2(current);
419
+ if (next === current) {
420
+ throw new Error("recursive mkdir returned a path outside the target parent chain");
421
+ }
422
+ current = next;
423
+ }
424
+ createdDirectories.push(firstCreatedParent);
425
+ syncDirectory(dirname2(firstCreatedParent));
426
+ for (const directory of createdDirectories.reverse().slice(0, -1)) {
427
+ syncDirectory(directory);
428
+ }
429
+ }
430
+ function ensureDurableDirectory(path, mode) {
431
+ const firstCreatedParent = mkdirSync(path, { recursive: true, mode });
432
+ if (firstCreatedParent) {
433
+ syncCreatedParentChain(firstCreatedParent, path);
434
+ }
435
+ }
436
+ function atomicWriteFile(target, content, options = {}) {
437
+ const parent = dirname2(target);
438
+ if (options.ensureParent) {
439
+ ensureDurableDirectory(parent);
440
+ }
441
+ const temporaryPath = `${target}.${randomUUID()}.tmp`;
442
+ let temporaryCreated = false;
443
+ try {
444
+ const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW;
445
+ const fd = openSync(temporaryPath, flags, options.mode ?? DEFAULT_FILE_MODE);
446
+ temporaryCreated = true;
447
+ try {
448
+ if (options.mode !== void 0) fchmodSync(fd, options.mode);
449
+ writeFileSync(fd, content);
450
+ fsyncSync(fd);
451
+ } finally {
452
+ closeSync(fd);
453
+ }
454
+ options.validate?.(temporaryPath);
455
+ renameSync(temporaryPath, target);
456
+ syncDirectory(parent);
457
+ } catch (error) {
458
+ if (temporaryCreated) {
459
+ try {
460
+ rmSync(temporaryPath, { force: true });
461
+ } catch {
462
+ }
463
+ }
464
+ throw error;
465
+ }
466
+ }
404
467
 
405
468
  // src/lib/file-lock.ts
406
469
  import { randomBytes } from "crypto";
@@ -18715,6 +18778,58 @@ function cachedCollectScopeAdmits(cwd, facts) {
18715
18778
  );
18716
18779
  }
18717
18780
 
18781
+ // src/lib/commit-heartbeat.ts
18782
+ import { execFileSync as execFileSync3 } from "child_process";
18783
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync8, readdirSync as readdirSync3, unlinkSync } from "fs";
18784
+ import { dirname as dirname8, join as join9, resolve as resolve3 } from "path";
18785
+ var FIRED_DIR = "prim/post-commit-fired";
18786
+ var GIT_TIMEOUT_MS3 = 1e3;
18787
+ var MAX_STAMP_BYTES = 1024;
18788
+ var MAX_FIRED_STAMPS = 256;
18789
+ var PRUNE_SLACK = 64;
18790
+ var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
18791
+ function gitPath(cwd, relative2) {
18792
+ const value = execFileSync3("git", ["rev-parse", "--git-path", relative2], {
18793
+ cwd,
18794
+ encoding: "utf8",
18795
+ stdio: ["ignore", "pipe", "ignore"],
18796
+ timeout: GIT_TIMEOUT_MS3
18797
+ }).replace(/\r?\n$/u, "");
18798
+ if (value === "" || /[\0\r\n]/u.test(value)) throw new Error("Git returned an unsafe path");
18799
+ return resolve3(cwd, value);
18800
+ }
18801
+ function ensurePrivateParent(path) {
18802
+ mkdirSync5(dirname8(dirname8(path)), { recursive: true, mode: 448 });
18803
+ mkdirSync5(dirname8(path), { recursive: true, mode: 448 });
18804
+ }
18805
+ function writeStamp(path, at) {
18806
+ ensurePrivateParent(path);
18807
+ atomicWriteFile(path, `${JSON.stringify({ at })}
18808
+ `, { mode: 384 });
18809
+ }
18810
+ function readStampAt(path) {
18811
+ try {
18812
+ const raw = readFileSync8(path, "utf8");
18813
+ if (raw.length > MAX_STAMP_BYTES) return void 0;
18814
+ const parsed = JSON.parse(raw);
18815
+ return typeof parsed.at === "number" && Number.isSafeInteger(parsed.at) && parsed.at > 0 ? parsed.at : void 0;
18816
+ } catch {
18817
+ return void 0;
18818
+ }
18819
+ }
18820
+ function recordPostCommitFired(cwd, sha, now = Date.now()) {
18821
+ if (!sha || !FULL_SHA_RE.test(sha)) return;
18822
+ try {
18823
+ const dir = gitPath(cwd, FIRED_DIR);
18824
+ writeStamp(join9(dir, sha), now);
18825
+ const names = readdirSync3(dir).filter((name) => FULL_SHA_RE.test(name));
18826
+ if (names.length <= MAX_FIRED_STAMPS + PRUNE_SLACK) return;
18827
+ const stale = names.map((name) => ({ name, at: readStampAt(join9(dir, name)) ?? 0 })).sort((left, right) => right.at - left.at || left.name.localeCompare(right.name)).slice(MAX_FIRED_STAMPS);
18828
+ for (const { name } of stale) unlinkSync(join9(dir, name));
18829
+ } catch {
18830
+ }
18831
+ }
18832
+
18718
18833
  // src/lib/unattended.ts
18719
18834
  var UNATTENDED_ENV = "PRIM_UNATTENDED";
18720
18835
  function unattendedEnv(env = process.env) {
@@ -18722,7 +18837,7 @@ function unattendedEnv(env = process.env) {
18722
18837
  }
18723
18838
 
18724
18839
  // src/lib/workspace-id.ts
18725
- import { execFileSync as execFileSync3 } from "child_process";
18840
+ import { execFileSync as execFileSync4 } from "child_process";
18726
18841
  import { randomUUID as randomUUID2 } from "crypto";
18727
18842
  import {
18728
18843
  constants as constants2,
@@ -18732,15 +18847,15 @@ import {
18732
18847
  fsyncSync as fsyncSync2,
18733
18848
  linkSync,
18734
18849
  lstatSync as lstatSync2,
18735
- mkdirSync as mkdirSync5,
18850
+ mkdirSync as mkdirSync6,
18736
18851
  openSync as openSync3,
18737
- readFileSync as readFileSync8,
18852
+ readFileSync as readFileSync9,
18738
18853
  statSync as statSync2,
18739
- unlinkSync,
18854
+ unlinkSync as unlinkSync2,
18740
18855
  writeFileSync as writeFileSync3
18741
18856
  } from "fs";
18742
- import { dirname as dirname8, isAbsolute as isAbsolute3, join as join9, resolve as resolve3 } from "path";
18743
- var WORKSPACE_ID_RELATIVE_PATH = join9("prim", "workspace-id");
18857
+ import { dirname as dirname9, isAbsolute as isAbsolute3, join as join10, resolve as resolve4 } from "path";
18858
+ var WORKSPACE_ID_RELATIVE_PATH = join10("prim", "workspace-id");
18744
18859
  var MAX_ANCESTORS = 64;
18745
18860
  var MAX_GIT_FILE_BYTES = 4096;
18746
18861
  var MAX_ID_FILE_BYTES = 128;
@@ -18768,7 +18883,7 @@ function resolveWorkspaceIdPath(cwd) {
18768
18883
  if (fromFilesystem) return { status: "resolved", path: fromFilesystem };
18769
18884
  }
18770
18885
  try {
18771
- const output = execFileSync3(
18886
+ const output = execFileSync4(
18772
18887
  "git",
18773
18888
  ["rev-parse", "--is-inside-work-tree", "--git-path", WORKSPACE_ID_RELATIVE_PATH],
18774
18889
  {
@@ -18780,13 +18895,13 @@ function resolveWorkspaceIdPath(cwd) {
18780
18895
  windowsHide: true
18781
18896
  }
18782
18897
  );
18783
- const [insideWorktree, gitPath, ...extra] = output.trim().split(/\r?\n/);
18784
- if (insideWorktree !== "true" || !gitPath || extra.length > 0 || gitPath.includes("\0")) {
18898
+ const [insideWorktree, gitPath2, ...extra] = output.trim().split(/\r?\n/);
18899
+ if (insideWorktree !== "true" || !gitPath2 || extra.length > 0 || gitPath2.includes("\0")) {
18785
18900
  return { status: "not_git" };
18786
18901
  }
18787
18902
  return {
18788
18903
  status: "resolved",
18789
- path: isAbsolute3(gitPath) ? resolve3(gitPath) : resolve3(cwd, gitPath)
18904
+ path: isAbsolute3(gitPath2) ? resolve4(gitPath2) : resolve4(cwd, gitPath2)
18790
18905
  };
18791
18906
  } catch (error) {
18792
18907
  const code = errorCode(error);
@@ -18797,21 +18912,21 @@ function resolveWorkspaceIdPath(cwd) {
18797
18912
  }
18798
18913
  }
18799
18914
  function resolveFromFilesystem(cwd) {
18800
- let current = resolve3(cwd);
18915
+ let current = resolve4(cwd);
18801
18916
  for (let depth = 0; depth < MAX_ANCESTORS; depth += 1) {
18802
- const dotGit = join9(current, ".git");
18917
+ const dotGit = join10(current, ".git");
18803
18918
  try {
18804
18919
  const stat = lstatSync2(dotGit);
18805
18920
  if (stat.isDirectory() && isGitDirectory(dotGit)) {
18806
- return join9(dotGit, WORKSPACE_ID_RELATIVE_PATH);
18921
+ return join10(dotGit, WORKSPACE_ID_RELATIVE_PATH);
18807
18922
  }
18808
18923
  if (stat.isFile() && stat.size <= MAX_GIT_FILE_BYTES) {
18809
- const contents = readFileSync8(dotGit, "utf8");
18924
+ const contents = readFileSync9(dotGit, "utf8");
18810
18925
  const match = /^gitdir: (.+?)(?:\r?\n)?$/.exec(contents);
18811
18926
  if (match?.[1]) {
18812
- const gitDirectory = isAbsolute3(match[1]) ? resolve3(match[1]) : resolve3(current, match[1]);
18927
+ const gitDirectory = isAbsolute3(match[1]) ? resolve4(match[1]) : resolve4(current, match[1]);
18813
18928
  if (isGitDirectory(gitDirectory)) {
18814
- return join9(gitDirectory, WORKSPACE_ID_RELATIVE_PATH);
18929
+ return join10(gitDirectory, WORKSPACE_ID_RELATIVE_PATH);
18815
18930
  }
18816
18931
  }
18817
18932
  }
@@ -18822,7 +18937,7 @@ function resolveFromFilesystem(cwd) {
18822
18937
  return null;
18823
18938
  }
18824
18939
  }
18825
- const parent = dirname8(current);
18940
+ const parent = dirname9(current);
18826
18941
  if (parent === current) break;
18827
18942
  current = parent;
18828
18943
  }
@@ -18830,7 +18945,7 @@ function resolveFromFilesystem(cwd) {
18830
18945
  }
18831
18946
  function isGitDirectory(path) {
18832
18947
  try {
18833
- return statSync2(join9(path, "HEAD")).isFile();
18948
+ return statSync2(join10(path, "HEAD")).isFile();
18834
18949
  } catch {
18835
18950
  return false;
18836
18951
  }
@@ -18849,7 +18964,7 @@ function inspectPath(path) {
18849
18964
  return { status: "corrupt", path, reason: "invalid_content" };
18850
18965
  }
18851
18966
  try {
18852
- const contents = readFileSync8(path, "utf8");
18967
+ const contents = readFileSync9(path, "utf8");
18853
18968
  const workspaceId = contents.endsWith("\n") ? contents.slice(0, -1) : contents;
18854
18969
  if ((contents === workspaceId || contents === `${workspaceId}
18855
18970
  `) && isCanonicalWorkspaceId(workspaceId)) {
@@ -18866,13 +18981,13 @@ function inspectPath(path) {
18866
18981
  }
18867
18982
  }
18868
18983
  function createWorkspaceId(path) {
18869
- const parent = dirname8(path);
18984
+ const parent = dirname9(path);
18870
18985
  const workspaceId = randomUUID2();
18871
- const temporaryPath = join9(parent, `.workspace-id-${process.pid}-${randomUUID2()}.tmp`);
18986
+ const temporaryPath = join10(parent, `.workspace-id-${process.pid}-${randomUUID2()}.tmp`);
18872
18987
  let temporaryCreated = false;
18873
18988
  let publishing = false;
18874
18989
  try {
18875
- mkdirSync5(parent, { recursive: true, mode: 448 });
18990
+ mkdirSync6(parent, { recursive: true, mode: 448 });
18876
18991
  chmodSync3(parent, 448);
18877
18992
  const fd = openSync3(
18878
18993
  temporaryPath,
@@ -18902,7 +19017,7 @@ function createWorkspaceId(path) {
18902
19017
  } finally {
18903
19018
  if (temporaryCreated) {
18904
19019
  try {
18905
- unlinkSync(temporaryPath);
19020
+ unlinkSync2(temporaryPath);
18906
19021
  } catch {
18907
19022
  }
18908
19023
  }
@@ -18937,8 +19052,8 @@ var AGENT_ENVELOPE_VERSION = 3;
18937
19052
  var ENVELOPE_VERSION = LEGACY_ENVELOPE_VERSION;
18938
19053
 
18939
19054
  // src/hooks/redact.ts
18940
- import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
18941
- import { join as join10 } from "path";
19055
+ import { existsSync as existsSync7, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
19056
+ import { join as join11 } from "path";
18942
19057
  import { Worker } from "worker_threads";
18943
19058
  var USER_HOME_RULES = [
18944
19059
  {
@@ -19114,9 +19229,9 @@ function toCommitMove(commit, cliVersion, cwd, context = {}) {
19114
19229
  }
19115
19230
 
19116
19231
  // src/hooks/post-commit.ts
19117
- var here = dirname9(fileURLToPath(import.meta.url));
19118
- var GIT_TIMEOUT_MS3 = 1e3;
19119
- var FULL_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
19232
+ var here = dirname10(fileURLToPath(import.meta.url));
19233
+ var GIT_TIMEOUT_MS4 = 1e3;
19234
+ var FULL_SHA_RE2 = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u;
19120
19235
  var OBSERVATION_MARKER_RE = /^prim-post-commit-observed\.[A-Za-z0-9]+$/u;
19121
19236
  var MAX_OBSERVATION_MARKER_AGE_MS = 10 * 60 * 1e3;
19122
19237
  var MAX_OBSERVATION_MARKER_FUTURE_SKEW_MS = 5e3;
@@ -19137,10 +19252,10 @@ function capturedAtFromLauncher(env = process.env, now = Date.now()) {
19137
19252
  }
19138
19253
  function gitText(args) {
19139
19254
  try {
19140
- return execFileSync4("git", args, {
19255
+ return execFileSync5("git", args, {
19141
19256
  encoding: "utf-8",
19142
19257
  stdio: ["ignore", "pipe", "ignore"],
19143
- timeout: GIT_TIMEOUT_MS3
19258
+ timeout: GIT_TIMEOUT_MS4
19144
19259
  }).trim();
19145
19260
  } catch {
19146
19261
  return;
@@ -19148,10 +19263,10 @@ function gitText(args) {
19148
19263
  }
19149
19264
  function gitBytes(args) {
19150
19265
  try {
19151
- return execFileSync4("git", args, {
19266
+ return execFileSync5("git", args, {
19152
19267
  encoding: "buffer",
19153
19268
  stdio: ["ignore", "pipe", "ignore"],
19154
- timeout: GIT_TIMEOUT_MS3
19269
+ timeout: GIT_TIMEOUT_MS4
19155
19270
  });
19156
19271
  } catch {
19157
19272
  return;
@@ -19182,7 +19297,7 @@ function parseNulPaths(value) {
19182
19297
  function readCommit() {
19183
19298
  const snapshotProvided = process.env.PRIM_COMMIT_SHA !== void 0;
19184
19299
  const sha = snapshotProvided ? process.env.PRIM_COMMIT_SHA : gitText(["rev-parse", "HEAD"]);
19185
- if (!sha || !FULL_SHA_RE.test(sha)) {
19300
+ if (!sha || !FULL_SHA_RE2.test(sha)) {
19186
19301
  return null;
19187
19302
  }
19188
19303
  const rawBranch = process.env.PRIM_COMMIT_BRANCH !== void 0 ? process.env.PRIM_COMMIT_BRANCH : gitText(["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -19208,14 +19323,14 @@ function readCommit() {
19208
19323
  }
19209
19324
  function resolveCliVersion() {
19210
19325
  try {
19211
- const pkg = JSON.parse(readFileSync10(join11(here, "..", "..", "package.json"), "utf-8"));
19326
+ const pkg = JSON.parse(readFileSync11(join12(here, "..", "..", "package.json"), "utf-8"));
19212
19327
  return pkg.version ?? "unknown";
19213
19328
  } catch {
19214
19329
  return "unknown";
19215
19330
  }
19216
19331
  }
19217
19332
  function spawnBackgroundFlush() {
19218
- const entry = join11(here, "..", "index.js");
19333
+ const entry = join12(here, "..", "index.js");
19219
19334
  spawn(process.execPath, [entry, "moves", "flush"], {
19220
19335
  detached: true,
19221
19336
  stdio: "ignore",
@@ -19224,6 +19339,7 @@ function spawnBackgroundFlush() {
19224
19339
  }
19225
19340
  function runPostCommit() {
19226
19341
  const cwd = gitText(["rev-parse", "--show-toplevel"]) ?? process.cwd();
19342
+ recordPostCommitFired(cwd, process.env.PRIM_COMMIT_SHA);
19227
19343
  if (!isRepoActiveForCapture(cwd)) {
19228
19344
  return;
19229
19345
  }
@@ -19998,6 +19998,13 @@ function shellQuote(value) {
19998
19998
  }
19999
19999
  var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
20000
20000
  var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
20001
+ var PRIM_CONFIG_SHELL_RESOLVER = [
20002
+ 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
20003
+ 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
20004
+ 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
20005
+ 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
20006
+ "fi; fi; "
20007
+ ].join("");
20001
20008
  function stableHookCommand(bin, args = "") {
20002
20009
  if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
20003
20010
  throw new Error("invalid stable hook command");
@@ -20007,11 +20014,7 @@ function stableHookCommand(bin, args = "") {
20007
20014
  // it as inert data during the rolling window so an older uninstall or
20008
20015
  // reinstall can remove the stable entry without invoking npm or PATH.
20009
20016
  `prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
20010
- 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
20011
- 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
20012
- 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
20013
- 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
20014
- "fi; fi; ",
20017
+ PRIM_CONFIG_SHELL_RESOLVER,
20015
20018
  `case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
20016
20019
  'exec "$prim_launcher" "$@"'
20017
20020
  ].join("");
@@ -20661,6 +20664,53 @@ import {
20661
20664
  writeFileSync as writeFileSync5
20662
20665
  } from "fs";
20663
20666
  import { delimiter, dirname as dirname8, isAbsolute as isAbsolute5, join as join11, resolve as resolve4 } from "path";
20667
+
20668
+ // src/lib/git-hook-contract.ts
20669
+ var GIT_HOOK_ENTRYPOINT_CONTENT = `#!/bin/sh
20670
+ # prim git hook entrypoint v1 \u2014 managed by prim; do not edit.
20671
+ # Run by the "# >>> prim <hook> hook >>>" block in a Git hook file. Acts only
20672
+ # where prim is active (git config prim.active true) and never fails the hook.
20673
+ case "$0" in */*) prim_dir=\${0%/*} ;; *) exit 0 ;; esac
20674
+ prim_launcher="$prim_dir/${STABLE_HOOK_LAUNCHER_NAME}"
20675
+ [ -x "$prim_launcher" ] || exit 0
20676
+ [ "$(git config --get prim.active 2>/dev/null)" = "true" ] || exit 0
20677
+ prim_hook=\${1-}
20678
+ if [ "$#" -gt 0 ]; then shift; fi
20679
+ case "$prim_hook" in
20680
+ pre-commit)
20681
+ "$prim_launcher" prim-pre-commit "$@" || :
20682
+ ;;
20683
+ post-commit)
20684
+ prim_sha=$(git rev-parse --verify HEAD 2>/dev/null) || exit 0
20685
+ case "$prim_sha" in *[!0-9a-f]* | "") exit 0 ;; esac
20686
+ case "\${#prim_sha}" in 40 | 64) ;; *) exit 0 ;; esac
20687
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
20688
+ prim_observed=$(mktemp "\${TMPDIR:-/tmp}/prim-post-commit-observed.XXXXXXXX" 2>/dev/null) || prim_observed=
20689
+ (
20690
+ trap '' HUP
20691
+ trap 'if [ -n "$prim_observed" ]; then rm -f "$prim_observed"; fi' 0
20692
+ PRIM_COMMIT_SHA=$prim_sha PRIM_COMMIT_BRANCH=$prim_branch PRIM_COMMIT_OBSERVED_FILE=$prim_observed "$prim_launcher" prim-post-commit
20693
+ ) </dev/null >/dev/null 2>&1 &
20694
+ ;;
20695
+ post-rewrite)
20696
+ case "\${1-}" in amend | rebase) prim_source=$1 ;; *) exit 0 ;; esac
20697
+ prim_pairs=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || exit 0
20698
+ if ! chmod 600 "$prim_pairs" 2>/dev/null || ! cat >"$prim_pairs"; then
20699
+ rm -f "$prim_pairs"
20700
+ exit 0
20701
+ fi
20702
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
20703
+ (
20704
+ trap '' HUP
20705
+ trap 'rm -f "$prim_pairs"' 0
20706
+ PRIM_REWRITE_SOURCE=$prim_source PRIM_REWRITE_BRANCH=$prim_branch PRIM_REWRITE_PAIRS_FILE=$prim_pairs "$prim_launcher" prim-post-rewrite
20707
+ ) </dev/null >/dev/null 2>&1 &
20708
+ ;;
20709
+ esac
20710
+ exit 0
20711
+ `;
20712
+
20713
+ // src/lib/hook-runtime.ts
20664
20714
  var RELEASE_PREFIX = "release-";
20665
20715
  var MAX_MANIFEST_BYTES = 64 * 1024;
20666
20716
  var RELEASE_GRACE_MS = 10 * 60 * 1e3;
@@ -370,6 +370,13 @@ function shellQuote(value) {
370
370
  }
371
371
  var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
372
372
  var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
373
+ var PRIM_CONFIG_SHELL_RESOLVER = [
374
+ 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
375
+ 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
376
+ 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
377
+ 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
378
+ "fi; fi; "
379
+ ].join("");
373
380
  function stableHookCommand(bin, args = "") {
374
381
  if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
375
382
  throw new Error("invalid stable hook command");
@@ -379,11 +386,7 @@ function stableHookCommand(bin, args = "") {
379
386
  // it as inert data during the rolling window so an older uninstall or
380
387
  // reinstall can remove the stable entry without invoking npm or PATH.
381
388
  `prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
382
- 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
383
- 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
384
- 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
385
- 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
386
- "fi; fi; ",
389
+ PRIM_CONFIG_SHELL_RESOLVER,
387
390
  `case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
388
391
  'exec "$prim_launcher" "$@"'
389
392
  ].join("");
@@ -20292,6 +20295,53 @@ import {
20292
20295
  writeFileSync as writeFileSync4
20293
20296
  } from "fs";
20294
20297
  import { delimiter, dirname as dirname6, isAbsolute as isAbsolute4, join as join9, resolve as resolve3 } from "path";
20298
+
20299
+ // src/lib/git-hook-contract.ts
20300
+ var GIT_HOOK_ENTRYPOINT_CONTENT = `#!/bin/sh
20301
+ # prim git hook entrypoint v1 \u2014 managed by prim; do not edit.
20302
+ # Run by the "# >>> prim <hook> hook >>>" block in a Git hook file. Acts only
20303
+ # where prim is active (git config prim.active true) and never fails the hook.
20304
+ case "$0" in */*) prim_dir=\${0%/*} ;; *) exit 0 ;; esac
20305
+ prim_launcher="$prim_dir/${STABLE_HOOK_LAUNCHER_NAME}"
20306
+ [ -x "$prim_launcher" ] || exit 0
20307
+ [ "$(git config --get prim.active 2>/dev/null)" = "true" ] || exit 0
20308
+ prim_hook=\${1-}
20309
+ if [ "$#" -gt 0 ]; then shift; fi
20310
+ case "$prim_hook" in
20311
+ pre-commit)
20312
+ "$prim_launcher" prim-pre-commit "$@" || :
20313
+ ;;
20314
+ post-commit)
20315
+ prim_sha=$(git rev-parse --verify HEAD 2>/dev/null) || exit 0
20316
+ case "$prim_sha" in *[!0-9a-f]* | "") exit 0 ;; esac
20317
+ case "\${#prim_sha}" in 40 | 64) ;; *) exit 0 ;; esac
20318
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
20319
+ prim_observed=$(mktemp "\${TMPDIR:-/tmp}/prim-post-commit-observed.XXXXXXXX" 2>/dev/null) || prim_observed=
20320
+ (
20321
+ trap '' HUP
20322
+ trap 'if [ -n "$prim_observed" ]; then rm -f "$prim_observed"; fi' 0
20323
+ PRIM_COMMIT_SHA=$prim_sha PRIM_COMMIT_BRANCH=$prim_branch PRIM_COMMIT_OBSERVED_FILE=$prim_observed "$prim_launcher" prim-post-commit
20324
+ ) </dev/null >/dev/null 2>&1 &
20325
+ ;;
20326
+ post-rewrite)
20327
+ case "\${1-}" in amend | rebase) prim_source=$1 ;; *) exit 0 ;; esac
20328
+ prim_pairs=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || exit 0
20329
+ if ! chmod 600 "$prim_pairs" 2>/dev/null || ! cat >"$prim_pairs"; then
20330
+ rm -f "$prim_pairs"
20331
+ exit 0
20332
+ fi
20333
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
20334
+ (
20335
+ trap '' HUP
20336
+ trap 'rm -f "$prim_pairs"' 0
20337
+ PRIM_REWRITE_SOURCE=$prim_source PRIM_REWRITE_BRANCH=$prim_branch PRIM_REWRITE_PAIRS_FILE=$prim_pairs "$prim_launcher" prim-post-rewrite
20338
+ ) </dev/null >/dev/null 2>&1 &
20339
+ ;;
20340
+ esac
20341
+ exit 0
20342
+ `;
20343
+
20344
+ // src/lib/hook-runtime.ts
20295
20345
  var RELEASE_PREFIX = "release-";
20296
20346
  var MAX_MANIFEST_BYTES = 64 * 1024;
20297
20347
  var RELEASE_GRACE_MS = 10 * 60 * 1e3;
@@ -20180,6 +20180,13 @@ function shellQuote(value) {
20180
20180
  }
20181
20181
  var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
20182
20182
  var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
20183
+ var PRIM_CONFIG_SHELL_RESOLVER = [
20184
+ 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
20185
+ 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
20186
+ 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
20187
+ 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
20188
+ "fi; fi; "
20189
+ ].join("");
20183
20190
  function stableHookCommand(bin, args = "") {
20184
20191
  if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
20185
20192
  throw new Error("invalid stable hook command");
@@ -20189,11 +20196,7 @@ function stableHookCommand(bin, args = "") {
20189
20196
  // it as inert data during the rolling window so an older uninstall or
20190
20197
  // reinstall can remove the stable entry without invoking npm or PATH.
20191
20198
  `prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
20192
- 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
20193
- 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
20194
- 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
20195
- 'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
20196
- "fi; fi; ",
20199
+ PRIM_CONFIG_SHELL_RESOLVER,
20197
20200
  `case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
20198
20201
  'exec "$prim_launcher" "$@"'
20199
20202
  ].join("");
@@ -20822,6 +20825,53 @@ import {
20822
20825
  writeFileSync as writeFileSync5
20823
20826
  } from "fs";
20824
20827
  import { delimiter, dirname as dirname10, isAbsolute as isAbsolute5, join as join13, resolve as resolve4 } from "path";
20828
+
20829
+ // src/lib/git-hook-contract.ts
20830
+ var GIT_HOOK_ENTRYPOINT_CONTENT = `#!/bin/sh
20831
+ # prim git hook entrypoint v1 \u2014 managed by prim; do not edit.
20832
+ # Run by the "# >>> prim <hook> hook >>>" block in a Git hook file. Acts only
20833
+ # where prim is active (git config prim.active true) and never fails the hook.
20834
+ case "$0" in */*) prim_dir=\${0%/*} ;; *) exit 0 ;; esac
20835
+ prim_launcher="$prim_dir/${STABLE_HOOK_LAUNCHER_NAME}"
20836
+ [ -x "$prim_launcher" ] || exit 0
20837
+ [ "$(git config --get prim.active 2>/dev/null)" = "true" ] || exit 0
20838
+ prim_hook=\${1-}
20839
+ if [ "$#" -gt 0 ]; then shift; fi
20840
+ case "$prim_hook" in
20841
+ pre-commit)
20842
+ "$prim_launcher" prim-pre-commit "$@" || :
20843
+ ;;
20844
+ post-commit)
20845
+ prim_sha=$(git rev-parse --verify HEAD 2>/dev/null) || exit 0
20846
+ case "$prim_sha" in *[!0-9a-f]* | "") exit 0 ;; esac
20847
+ case "\${#prim_sha}" in 40 | 64) ;; *) exit 0 ;; esac
20848
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
20849
+ prim_observed=$(mktemp "\${TMPDIR:-/tmp}/prim-post-commit-observed.XXXXXXXX" 2>/dev/null) || prim_observed=
20850
+ (
20851
+ trap '' HUP
20852
+ trap 'if [ -n "$prim_observed" ]; then rm -f "$prim_observed"; fi' 0
20853
+ PRIM_COMMIT_SHA=$prim_sha PRIM_COMMIT_BRANCH=$prim_branch PRIM_COMMIT_OBSERVED_FILE=$prim_observed "$prim_launcher" prim-post-commit
20854
+ ) </dev/null >/dev/null 2>&1 &
20855
+ ;;
20856
+ post-rewrite)
20857
+ case "\${1-}" in amend | rebase) prim_source=$1 ;; *) exit 0 ;; esac
20858
+ prim_pairs=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || exit 0
20859
+ if ! chmod 600 "$prim_pairs" 2>/dev/null || ! cat >"$prim_pairs"; then
20860
+ rm -f "$prim_pairs"
20861
+ exit 0
20862
+ fi
20863
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
20864
+ (
20865
+ trap '' HUP
20866
+ trap 'rm -f "$prim_pairs"' 0
20867
+ PRIM_REWRITE_SOURCE=$prim_source PRIM_REWRITE_BRANCH=$prim_branch PRIM_REWRITE_PAIRS_FILE=$prim_pairs "$prim_launcher" prim-post-rewrite
20868
+ ) </dev/null >/dev/null 2>&1 &
20869
+ ;;
20870
+ esac
20871
+ exit 0
20872
+ `;
20873
+
20874
+ // src/lib/hook-runtime.ts
20825
20875
  var RELEASE_PREFIX = "release-";
20826
20876
  var MAX_MANIFEST_BYTES = 64 * 1024;
20827
20877
  var RELEASE_GRACE_MS = 10 * 60 * 1e3;