@primitive.ai/prim 0.1.0-alpha.92 → 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.
Files changed (42) hide show
  1. package/README.md +84 -10
  2. package/SKILL.md +5 -2
  3. package/dist/{chunk-67VNLLBN.js → chunk-2GP5UFV7.js} +1 -0
  4. package/dist/{chunk-26P57LAR.js → chunk-AXQQMX2U.js} +203 -9
  5. package/dist/{chunk-M6RIJVRE.js → chunk-DSWT6BCC.js} +78 -4
  6. package/dist/{chunk-FBAYCS3X.js → chunk-DXJQR4QI.js} +150 -38
  7. package/dist/chunk-FXF4JWK2.js +162 -0
  8. package/dist/{chunk-PCVGBE5B.js → chunk-G2VMA2QO.js} +2 -1
  9. package/dist/{chunk-NAHHFSLH.js → chunk-ICM2XCXS.js} +2 -2
  10. package/dist/{chunk-5HRR4AOR.js → chunk-IMK4JKTA.js} +1 -1
  11. package/dist/{chunk-OAAP2MY6.js → chunk-K7D2EYBS.js} +2 -2
  12. package/dist/{chunk-ADUTW66W.js → chunk-O7YAW7G6.js} +6 -6
  13. package/dist/{chunk-5AFRMOPX.js → chunk-OH3EZXYX.js} +1 -1
  14. package/dist/{chunk-KFXF2V5G.js → chunk-OTHKG76J.js} +1 -1
  15. package/dist/chunk-OYHYCK4K.js +13 -0
  16. package/dist/{chunk-FKXFUUWJ.js → chunk-TYAHC25F.js} +1 -1
  17. package/dist/{chunk-5GYQ4J3D.js → chunk-UKUAAAXE.js} +2 -2
  18. package/dist/{chunk-LGGMJYY4.js → chunk-WUXFLE7X.js} +720 -369
  19. package/dist/{chunk-WX52U7Z2.js → chunk-ZU563PQI.js} +58 -2
  20. package/dist/daemon/server.js +372 -123
  21. package/dist/hook-runtime/daemon/server.js +372 -123
  22. package/dist/hook-runtime/hooks/post-commit.js +192 -58
  23. package/dist/hook-runtime/hooks/post-rewrite.js +58 -40
  24. package/dist/hook-runtime/hooks/post-tool-use.js +150 -67
  25. package/dist/hook-runtime/hooks/pre-tool-use.js +87 -15
  26. package/dist/hook-runtime/hooks/prim-hook.js +197 -107
  27. package/dist/hook-runtime/hooks/session-end.js +53 -5
  28. package/dist/hook-runtime/hooks/session-start.js +880 -280
  29. package/dist/hook-runtime/index.js +4008 -2438
  30. package/dist/hook-runtime/statusline-main.js +23 -1
  31. package/dist/hooks/post-commit.js +15 -8
  32. package/dist/hooks/post-rewrite.js +11 -8
  33. package/dist/hooks/post-tool-use.js +13 -15
  34. package/dist/hooks/pre-commit.js +4 -4
  35. package/dist/hooks/pre-tool-use.js +10 -12
  36. package/dist/hooks/prim-hook.js +17 -14
  37. package/dist/hooks/session-end.js +4 -4
  38. package/dist/hooks/session-start.js +35 -45
  39. package/dist/index.js +1401 -620
  40. package/dist/statusline-main.js +23 -1
  41. package/package.json +1 -1
  42. package/dist/chunk-BTNNEH2W.js +0 -79
@@ -59,10 +59,10 @@ import {
59
59
  fstatSync as fstatSync2,
60
60
  lstatSync as lstatSync3,
61
61
  openSync as openSync4,
62
- readFileSync as readFileSync9,
62
+ readFileSync as readFileSync10,
63
63
  unlinkSync as unlinkSync2
64
64
  } from "fs";
65
- import { basename as basename2, dirname as dirname8, isAbsolute as isAbsolute4, join as join10 } from "path";
65
+ import { basename as basename3, dirname as dirname9, isAbsolute as isAbsolute4, join as join11 } from "path";
66
66
  import { fileURLToPath } from "url";
67
67
 
68
68
  // src/binding.ts
@@ -379,16 +379,16 @@ function resolveOrg(args) {
379
379
  import {
380
380
  appendFileSync,
381
381
  closeSync as closeSync2,
382
- existsSync as existsSync4,
382
+ existsSync as existsSync5,
383
383
  fstatSync,
384
384
  mkdirSync as mkdirSync4,
385
385
  openSync as openSync2,
386
386
  opendirSync,
387
- readFileSync as readFileSync5,
387
+ readFileSync as readFileSync6,
388
388
  readSync,
389
- readdirSync
389
+ readdirSync as readdirSync2
390
390
  } from "fs";
391
- import { dirname as dirname5, join as join6 } from "path";
391
+ import { dirname as dirname6, join as join7 } from "path";
392
392
 
393
393
  // src/client.ts
394
394
  import { createHash as createHash2 } from "crypto";
@@ -426,8 +426,19 @@ function getSiteUrlForEnvironment(primApiUrl) {
426
426
  }
427
427
  var CLI_MANAGEMENT_RESPONSE_MAX_BYTES = 2 * 1024 * 1024;
428
428
 
429
+ // src/drain-progress.ts
430
+ import {
431
+ chmodSync as chmodSync2,
432
+ existsSync as existsSync4,
433
+ readFileSync as readFileSync5,
434
+ readdirSync,
435
+ rmSync as rmSync4,
436
+ rmdirSync
437
+ } from "fs";
438
+ import { basename, dirname as dirname5, join as join6 } from "path";
439
+
429
440
  // src/journal.ts
430
- var JOURNAL_DIR = join6(primConfigDirectory(), "moves");
441
+ var JOURNAL_DIR = join7(primConfigDirectory(), "moves");
431
442
  var UNBOUND_BUCKET = "_unbound";
432
443
  var JOURNAL_BASENAME = "journal.ndjson";
433
444
  var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
@@ -439,15 +450,15 @@ function envSlug(apiUrl) {
439
450
  return slug;
440
451
  }
441
452
  function currentEnvDir() {
442
- return join6(JOURNAL_DIR, envSlug(getSiteUrl()));
453
+ return join7(JOURNAL_DIR, envSlug(getSiteUrl()));
443
454
  }
444
455
  var DIR_MODE = 448;
445
456
  var FILE_MODE = 384;
446
457
  var SAFE_BUCKET = /^[A-Za-z0-9_-]+$/;
447
458
  var RESERVED_BUCKETS = /* @__PURE__ */ new Set([UNBOUND_BUCKET, "_legacy"]);
448
459
  function appendMoveToPath(path, move) {
449
- const dir = dirname5(path);
450
- if (!existsSync4(dir)) {
460
+ const dir = dirname6(path);
461
+ if (!existsSync5(dir)) {
451
462
  mkdirSync4(dir, { recursive: true, mode: DIR_MODE });
452
463
  }
453
464
  appendFileSync(path, `${JSON.stringify(move)}
@@ -455,10 +466,10 @@ function appendMoveToPath(path, move) {
455
466
  }
456
467
  function bucketDir(orgId) {
457
468
  const safe = orgId !== void 0 && SAFE_BUCKET.test(orgId) && !RESERVED_BUCKETS.has(orgId);
458
- return join6(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
469
+ return join7(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
459
470
  }
460
471
  function journalPath(orgId) {
461
- return join6(bucketDir(orgId), JOURNAL_BASENAME);
472
+ return join7(bucketDir(orgId), JOURNAL_BASENAME);
462
473
  }
463
474
  function appendMove(move, orgId) {
464
475
  appendMoveToPath(journalPath(orgId), move);
@@ -467,15 +478,15 @@ var FLUSHING_PREFIX = `${JOURNAL_BASENAME}.flushing.`;
467
478
 
468
479
  // src/lib/activation.ts
469
480
  import { execFileSync as execFileSync2 } from "child_process";
470
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
481
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
471
482
  import { homedir as homedir2 } from "os";
472
- import { join as join7 } from "path";
483
+ import { join as join8 } from "path";
473
484
 
474
485
  // src/lib/git.ts
475
486
  import { execFileSync } from "child_process";
476
487
  import { createHash as createHash3 } from "crypto";
477
488
  import { lstatSync, realpathSync } from "fs";
478
- import { basename, dirname as dirname6, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "path";
489
+ import { basename as basename2, dirname as dirname7, isAbsolute as isAbsolute2, relative, resolve as resolve2, sep } from "path";
479
490
  var GIT_TIMEOUT_MS = 1e3;
480
491
  var GITHUB_OWNER_PATTERN = /^(?![A-Za-z0-9-]*--)[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u;
481
492
  var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]{1,100}$/u;
@@ -623,10 +634,10 @@ function hasProjectPrimInstall(cwd) {
623
634
  const root = gitToplevel(cwd);
624
635
  if (root === null || root === homedir2()) return false;
625
636
  for (const rel of PROJECT_INSTALL_FILES) {
626
- const path = join7(root, rel);
627
- if (existsSync5(path)) {
637
+ const path = join8(root, rel);
638
+ if (existsSync6(path)) {
628
639
  try {
629
- const content = readFileSync6(path, "utf-8");
640
+ const content = readFileSync7(path, "utf-8");
630
641
  if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
631
642
  } catch {
632
643
  }
@@ -18712,12 +18723,18 @@ function cachedCollectScopeAdmits(cwd, facts) {
18712
18723
  );
18713
18724
  }
18714
18725
 
18726
+ // src/lib/unattended.ts
18727
+ var UNATTENDED_ENV = "PRIM_UNATTENDED";
18728
+ function unattendedEnv(env = process.env) {
18729
+ return { ...env, [UNATTENDED_ENV]: "1" };
18730
+ }
18731
+
18715
18732
  // src/lib/workspace-id.ts
18716
18733
  import { execFileSync as execFileSync3 } from "child_process";
18717
18734
  import { randomUUID as randomUUID2 } from "crypto";
18718
18735
  import {
18719
18736
  constants as constants2,
18720
- chmodSync as chmodSync2,
18737
+ chmodSync as chmodSync3,
18721
18738
  closeSync as closeSync3,
18722
18739
  fchmodSync as fchmodSync2,
18723
18740
  fsyncSync as fsyncSync2,
@@ -18725,13 +18742,13 @@ import {
18725
18742
  lstatSync as lstatSync2,
18726
18743
  mkdirSync as mkdirSync5,
18727
18744
  openSync as openSync3,
18728
- readFileSync as readFileSync7,
18745
+ readFileSync as readFileSync8,
18729
18746
  statSync as statSync2,
18730
18747
  unlinkSync,
18731
18748
  writeFileSync as writeFileSync3
18732
18749
  } from "fs";
18733
- import { dirname as dirname7, isAbsolute as isAbsolute3, join as join8, resolve as resolve3 } from "path";
18734
- var WORKSPACE_ID_RELATIVE_PATH = join8("prim", "workspace-id");
18750
+ import { dirname as dirname8, isAbsolute as isAbsolute3, join as join9, resolve as resolve3 } from "path";
18751
+ var WORKSPACE_ID_RELATIVE_PATH = join9("prim", "workspace-id");
18735
18752
  var MAX_ANCESTORS = 64;
18736
18753
  var MAX_GIT_FILE_BYTES = 4096;
18737
18754
  var MAX_ID_FILE_BYTES = 128;
@@ -18790,19 +18807,19 @@ function resolveWorkspaceIdPath(cwd) {
18790
18807
  function resolveFromFilesystem(cwd) {
18791
18808
  let current = resolve3(cwd);
18792
18809
  for (let depth = 0; depth < MAX_ANCESTORS; depth += 1) {
18793
- const dotGit = join8(current, ".git");
18810
+ const dotGit = join9(current, ".git");
18794
18811
  try {
18795
18812
  const stat = lstatSync2(dotGit);
18796
18813
  if (stat.isDirectory() && isGitDirectory(dotGit)) {
18797
- return join8(dotGit, WORKSPACE_ID_RELATIVE_PATH);
18814
+ return join9(dotGit, WORKSPACE_ID_RELATIVE_PATH);
18798
18815
  }
18799
18816
  if (stat.isFile() && stat.size <= MAX_GIT_FILE_BYTES) {
18800
- const contents = readFileSync7(dotGit, "utf8");
18817
+ const contents = readFileSync8(dotGit, "utf8");
18801
18818
  const match = /^gitdir: (.+?)(?:\r?\n)?$/.exec(contents);
18802
18819
  if (match?.[1]) {
18803
18820
  const gitDirectory = isAbsolute3(match[1]) ? resolve3(match[1]) : resolve3(current, match[1]);
18804
18821
  if (isGitDirectory(gitDirectory)) {
18805
- return join8(gitDirectory, WORKSPACE_ID_RELATIVE_PATH);
18822
+ return join9(gitDirectory, WORKSPACE_ID_RELATIVE_PATH);
18806
18823
  }
18807
18824
  }
18808
18825
  }
@@ -18813,7 +18830,7 @@ function resolveFromFilesystem(cwd) {
18813
18830
  return null;
18814
18831
  }
18815
18832
  }
18816
- const parent = dirname7(current);
18833
+ const parent = dirname8(current);
18817
18834
  if (parent === current) break;
18818
18835
  current = parent;
18819
18836
  }
@@ -18821,7 +18838,7 @@ function resolveFromFilesystem(cwd) {
18821
18838
  }
18822
18839
  function isGitDirectory(path) {
18823
18840
  try {
18824
- return statSync2(join8(path, "HEAD")).isFile();
18841
+ return statSync2(join9(path, "HEAD")).isFile();
18825
18842
  } catch {
18826
18843
  return false;
18827
18844
  }
@@ -18840,7 +18857,7 @@ function inspectPath(path) {
18840
18857
  return { status: "corrupt", path, reason: "invalid_content" };
18841
18858
  }
18842
18859
  try {
18843
- const contents = readFileSync7(path, "utf8");
18860
+ const contents = readFileSync8(path, "utf8");
18844
18861
  const workspaceId = contents.endsWith("\n") ? contents.slice(0, -1) : contents;
18845
18862
  if ((contents === workspaceId || contents === `${workspaceId}
18846
18863
  `) && isCanonicalWorkspaceId(workspaceId)) {
@@ -18857,14 +18874,14 @@ function inspectPath(path) {
18857
18874
  }
18858
18875
  }
18859
18876
  function createWorkspaceId(path) {
18860
- const parent = dirname7(path);
18877
+ const parent = dirname8(path);
18861
18878
  const workspaceId = randomUUID2();
18862
- const temporaryPath = join8(parent, `.workspace-id-${process.pid}-${randomUUID2()}.tmp`);
18879
+ const temporaryPath = join9(parent, `.workspace-id-${process.pid}-${randomUUID2()}.tmp`);
18863
18880
  let temporaryCreated = false;
18864
18881
  let publishing = false;
18865
18882
  try {
18866
18883
  mkdirSync5(parent, { recursive: true, mode: 448 });
18867
- chmodSync2(parent, 448);
18884
+ chmodSync3(parent, 448);
18868
18885
  const fd = openSync3(
18869
18886
  temporaryPath,
18870
18887
  constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY,
@@ -18927,8 +18944,8 @@ var LEGACY_ENVELOPE_VERSION = 1;
18927
18944
  var ENVELOPE_VERSION = LEGACY_ENVELOPE_VERSION;
18928
18945
 
18929
18946
  // src/hooks/redact.ts
18930
- import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
18931
- import { join as join9 } from "path";
18947
+ import { existsSync as existsSync7, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
18948
+ import { join as join10 } from "path";
18932
18949
  import { Worker } from "worker_threads";
18933
18950
  var USER_HOME_RULES = [
18934
18951
  {
@@ -19107,7 +19124,7 @@ function toRewriteMove(rewrite, cliVersion, cwd, context) {
19107
19124
  }
19108
19125
 
19109
19126
  // src/hooks/post-rewrite.ts
19110
- var here = dirname8(fileURLToPath(import.meta.url));
19127
+ var here = dirname9(fileURLToPath(import.meta.url));
19111
19128
  var GIT_TIMEOUT_MS3 = 1e3;
19112
19129
  var MAX_PAIRS_FILE_BYTES = 1048576;
19113
19130
  var MAX_PAIRS_FILE_AGE_MS = 10 * 60 * 1e3;
@@ -19137,7 +19154,7 @@ function privatePairsFileStat(stat, now) {
19137
19154
  }
19138
19155
  function rewritePairsFromLauncher(env = process.env, now = Date.now()) {
19139
19156
  const path = env.PRIM_REWRITE_PAIRS_FILE;
19140
- if (!path || !isAbsolute4(path) || !PAIRS_FILE_RE.test(basename2(path))) return;
19157
+ if (!path || !isAbsolute4(path) || !PAIRS_FILE_RE.test(basename3(path))) return;
19141
19158
  let fd;
19142
19159
  try {
19143
19160
  const pathStat = lstatSync3(path);
@@ -19147,7 +19164,7 @@ function rewritePairsFromLauncher(env = process.env, now = Date.now()) {
19147
19164
  if (!privatePairsFileStat(openStat, now) || openStat.dev !== pathStat.dev || openStat.ino !== pathStat.ino) {
19148
19165
  return;
19149
19166
  }
19150
- const bytes = readFileSync9(fd);
19167
+ const bytes = readFileSync10(fd);
19151
19168
  if (bytes.length !== openStat.size) return;
19152
19169
  let text;
19153
19170
  try {
@@ -19203,17 +19220,18 @@ function validBranch(value) {
19203
19220
  }
19204
19221
  function resolveCliVersion() {
19205
19222
  try {
19206
- const pkg = JSON.parse(readFileSync9(join10(here, "..", "..", "package.json"), "utf-8"));
19223
+ const pkg = JSON.parse(readFileSync10(join11(here, "..", "..", "package.json"), "utf-8"));
19207
19224
  return pkg.version ?? "unknown";
19208
19225
  } catch {
19209
19226
  return "unknown";
19210
19227
  }
19211
19228
  }
19212
19229
  function spawnBackgroundFlush() {
19213
- const entry = join10(here, "..", "index.js");
19230
+ const entry = join11(here, "..", "index.js");
19214
19231
  spawn(process.execPath, [entry, "moves", "flush"], {
19215
19232
  detached: true,
19216
- stdio: "ignore"
19233
+ stdio: "ignore",
19234
+ env: unattendedEnv()
19217
19235
  }).unref();
19218
19236
  }
19219
19237
  function runPostRewrite() {
@@ -52,8 +52,8 @@ var require_ucs2length = __commonJS({
52
52
  });
53
53
 
54
54
  // src/hooks/post-tool-use.ts
55
- import { readFileSync as readFileSync14 } from "fs";
56
- import { dirname as dirname11, join as join16 } from "path";
55
+ import { readFileSync as readFileSync15 } from "fs";
56
+ import { dirname as dirname12, join as join17 } from "path";
57
57
  import { fileURLToPath as fileURLToPath2 } from "url";
58
58
 
59
59
  // src/binding.ts
@@ -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("");
@@ -20022,6 +20025,67 @@ function detachedHookShimCommand(bin, args = "") {
20022
20025
  return `payload=$(cat); { trap '' HUP; printf '%s' "$payload" | { ${stableHookCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
20023
20026
  }
20024
20027
 
20028
+ // src/lib/ansi.ts
20029
+ var ANSI_CODES = {
20030
+ yellow: "\x1B[33m",
20031
+ magenta: "\x1B[35m",
20032
+ blue: "\x1B[34m",
20033
+ cyan: "\x1B[36m",
20034
+ green: "\x1B[32m",
20035
+ red: "\x1B[31m",
20036
+ // 256-color approximation of the orange trigger marker (xterm 208).
20037
+ orange: "\x1B[38;5;208m",
20038
+ gray: "\x1B[90m"
20039
+ };
20040
+ var ANSI_RESET = "\x1B[0m";
20041
+ var ANSI_BOLD = "\x1B[1m";
20042
+ function supportsColor() {
20043
+ if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
20044
+ return false;
20045
+ }
20046
+ return process.stderr.isTTY === true;
20047
+ }
20048
+ function color(text, c) {
20049
+ if (!supportsColor()) {
20050
+ return text;
20051
+ }
20052
+ return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
20053
+ }
20054
+ function bold(text) {
20055
+ if (!supportsColor()) {
20056
+ return text;
20057
+ }
20058
+ return `${ANSI_BOLD}${text}${ANSI_RESET}`;
20059
+ }
20060
+ var MAX_HEALTH_ERROR_LENGTH = 240;
20061
+ function boundedHealthError(value) {
20062
+ if (!value) return void 0;
20063
+ const clean = terminalSafeLine(value);
20064
+ if (!clean) return void 0;
20065
+ return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
20066
+ }
20067
+
20068
+ // src/lib/delivery-backlog.ts
20069
+ var MS_PER_SECOND = 1e3;
20070
+ var MS_PER_MINUTE = 60 * MS_PER_SECOND;
20071
+ var MS_PER_HOUR = 60 * MS_PER_MINUTE;
20072
+ var MS_PER_DAY = 24 * MS_PER_HOUR;
20073
+ function isNonNegativeInteger(value) {
20074
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
20075
+ }
20076
+ function isPositiveInteger(value) {
20077
+ return isNonNegativeInteger(value) && value > 0;
20078
+ }
20079
+ function deliveryBacklogState(ingestion) {
20080
+ if (ingestion?.healthy !== false) return void 0;
20081
+ const failures = ingestion.consecutiveFailures;
20082
+ const failedWithoutProgress = failures !== 0 && !(isPositiveInteger(failures) && isPositiveInteger(ingestion.lastFailedDrainAcknowledgedCount));
20083
+ if (failedWithoutProgress) return "failing";
20084
+ const retained = ingestion.lastRetainedBucketCount;
20085
+ if (!isNonNegativeInteger(retained)) return "failing";
20086
+ return retained > 0 ? "retained" : "draining";
20087
+ }
20088
+
20025
20089
  // src/lib/presence.ts
20026
20090
  var DECISION_ORIGIN = "https://app.getprimitive.ai";
20027
20091
  function validDecisionUrl(value) {
@@ -20096,7 +20160,8 @@ function formatStatusline(version, snapshot, resolveIngestionStatus, options = {
20096
20160
  if (snapshot.ingestion?.healthy === false) {
20097
20161
  const pending = snapshot.ingestion.pendingCount;
20098
20162
  const qualifier = snapshot.ingestion.pendingSampled ? "at least " : "";
20099
- return `primitive ${version} (daemon: degraded \xB7 delivery: stalled${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
20163
+ const delivery = deliveryBacklogState(snapshot.ingestion) === "draining" && snapshot.heartbeat?.healthy === true ? "draining" : "stalled";
20164
+ return `primitive ${version} (daemon: degraded \xB7 delivery: ${delivery}${typeof pending === "number" ? ` \xB7 ${qualifier}${String(pending)} pending` : ""}${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
20100
20165
  }
20101
20166
  if (snapshot.heartbeat?.healthy === false) {
20102
20167
  return `primitive ${version} (daemon: degraded \xB7 presence: unavailable${repositorySuffix}${ingestionSuffix(ingestionStatus)})`;
@@ -20599,6 +20664,53 @@ import {
20599
20664
  writeFileSync as writeFileSync5
20600
20665
  } from "fs";
20601
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
20602
20714
  var RELEASE_PREFIX = "release-";
20603
20715
  var MAX_MANIFEST_BYTES = 64 * 1024;
20604
20716
  var RELEASE_GRACE_MS = 10 * 60 * 1e3;
@@ -25682,17 +25794,30 @@ function requireDurableIngestAcknowledgement(value, expected, cwd = process.cwd(
25682
25794
  import {
25683
25795
  appendFileSync,
25684
25796
  closeSync as closeSync3,
25685
- existsSync as existsSync10,
25797
+ existsSync as existsSync11,
25686
25798
  fstatSync,
25687
25799
  mkdirSync as mkdirSync7,
25688
25800
  openSync as openSync3,
25689
25801
  opendirSync,
25690
- readFileSync as readFileSync12,
25802
+ readFileSync as readFileSync13,
25691
25803
  readSync,
25692
- readdirSync as readdirSync3
25804
+ readdirSync as readdirSync4
25693
25805
  } from "fs";
25694
- import { dirname as dirname10, join as join14 } from "path";
25695
- var JOURNAL_DIR = join14(primConfigDirectory(), "moves");
25806
+ import { dirname as dirname11, join as join15 } from "path";
25807
+
25808
+ // src/drain-progress.ts
25809
+ import {
25810
+ chmodSync as chmodSync5,
25811
+ existsSync as existsSync10,
25812
+ readFileSync as readFileSync12,
25813
+ readdirSync as readdirSync3,
25814
+ rmSync as rmSync6,
25815
+ rmdirSync
25816
+ } from "fs";
25817
+ import { basename as basename2, dirname as dirname10, join as join14 } from "path";
25818
+
25819
+ // src/journal.ts
25820
+ var JOURNAL_DIR = join15(primConfigDirectory(), "moves");
25696
25821
  var UNBOUND_BUCKET = "_unbound";
25697
25822
  var JOURNAL_BASENAME = "journal.ndjson";
25698
25823
  var JOURNAL_STATS_SAMPLE_BYTES = 64 * 1024;
@@ -25704,15 +25829,15 @@ function envSlug(apiUrl) {
25704
25829
  return slug;
25705
25830
  }
25706
25831
  function currentEnvDir() {
25707
- return join14(JOURNAL_DIR, envSlug(getSiteUrl()));
25832
+ return join15(JOURNAL_DIR, envSlug(getSiteUrl()));
25708
25833
  }
25709
25834
  var DIR_MODE = 448;
25710
25835
  var FILE_MODE2 = 384;
25711
25836
  var SAFE_BUCKET = /^[A-Za-z0-9_-]+$/;
25712
25837
  var RESERVED_BUCKETS = /* @__PURE__ */ new Set([UNBOUND_BUCKET, "_legacy"]);
25713
25838
  function appendMoveToPath(path, move) {
25714
- const dir = dirname10(path);
25715
- if (!existsSync10(dir)) {
25839
+ const dir = dirname11(path);
25840
+ if (!existsSync11(dir)) {
25716
25841
  mkdirSync7(dir, { recursive: true, mode: DIR_MODE });
25717
25842
  }
25718
25843
  appendFileSync(path, `${JSON.stringify(move)}
@@ -25720,10 +25845,10 @@ function appendMoveToPath(path, move) {
25720
25845
  }
25721
25846
  function bucketDir(orgId) {
25722
25847
  const safe = orgId !== void 0 && SAFE_BUCKET.test(orgId) && !RESERVED_BUCKETS.has(orgId);
25723
- return join14(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
25848
+ return join15(currentEnvDir(), safe ? orgId : UNBOUND_BUCKET);
25724
25849
  }
25725
25850
  function journalPath(orgId) {
25726
- return join14(bucketDir(orgId), JOURNAL_BASENAME);
25851
+ return join15(bucketDir(orgId), JOURNAL_BASENAME);
25727
25852
  }
25728
25853
  function appendMove(move, orgId) {
25729
25854
  appendMoveToPath(journalPath(orgId), move);
@@ -25750,51 +25875,9 @@ import { platform } from "os";
25750
25875
  var AGENT_ENVELOPE_VERSION = 3;
25751
25876
 
25752
25877
  // src/hooks/redact.ts
25753
- import { existsSync as existsSync11, readFileSync as readFileSync13, statSync as statSync5 } from "fs";
25754
- import { join as join15 } from "path";
25878
+ import { existsSync as existsSync12, readFileSync as readFileSync14, statSync as statSync5 } from "fs";
25879
+ import { join as join16 } from "path";
25755
25880
  import { Worker } from "worker_threads";
25756
-
25757
- // src/lib/ansi.ts
25758
- var ANSI_CODES = {
25759
- yellow: "\x1B[33m",
25760
- magenta: "\x1B[35m",
25761
- blue: "\x1B[34m",
25762
- cyan: "\x1B[36m",
25763
- green: "\x1B[32m",
25764
- red: "\x1B[31m",
25765
- // 256-color approximation of the orange trigger marker (xterm 208).
25766
- orange: "\x1B[38;5;208m",
25767
- gray: "\x1B[90m"
25768
- };
25769
- var ANSI_RESET = "\x1B[0m";
25770
- var ANSI_BOLD = "\x1B[1m";
25771
- function supportsColor() {
25772
- if (process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "") {
25773
- return false;
25774
- }
25775
- return process.stderr.isTTY === true;
25776
- }
25777
- function color(text, c) {
25778
- if (!supportsColor()) {
25779
- return text;
25780
- }
25781
- return `${ANSI_CODES[c]}${text}${ANSI_RESET}`;
25782
- }
25783
- function bold(text) {
25784
- if (!supportsColor()) {
25785
- return text;
25786
- }
25787
- return `${ANSI_BOLD}${text}${ANSI_RESET}`;
25788
- }
25789
- var MAX_HEALTH_ERROR_LENGTH = 240;
25790
- function boundedHealthError(value) {
25791
- if (!value) return void 0;
25792
- const clean = terminalSafeLine(value);
25793
- if (!clean) return void 0;
25794
- return clean.length <= MAX_HEALTH_ERROR_LENGTH ? clean : `${clean.slice(0, MAX_HEALTH_ERROR_LENGTH - 1)}\u2026`;
25795
- }
25796
-
25797
- // src/hooks/redact.ts
25798
25881
  var USER_HOME_RULES = [
25799
25882
  {
25800
25883
  pattern: /\/Users\/[^/\\\s"'`]+/g,
@@ -25869,15 +25952,15 @@ function normalizedWorkspaceFlags(value) {
25869
25952
  return [...new Set(`g${requested}`)].sort().join("");
25870
25953
  }
25871
25954
  function loadWorkspaceRules(cwd) {
25872
- const path = join15(cwd, WORKSPACE_FILE2);
25873
- if (!existsSync11(path)) return [];
25955
+ const path = join16(cwd, WORKSPACE_FILE2);
25956
+ if (!existsSync12(path)) return [];
25874
25957
  let cfg;
25875
25958
  try {
25876
25959
  if (statSync5(path).size > MAX_WORKSPACE_CONFIG_BYTES) {
25877
25960
  debugRedaction(`ignoring oversized ${WORKSPACE_FILE2}`);
25878
25961
  return [];
25879
25962
  }
25880
- cfg = JSON.parse(readFileSync13(path, "utf-8"));
25963
+ cfg = JSON.parse(readFileSync14(path, "utf-8"));
25881
25964
  } catch {
25882
25965
  debugRedaction(`ignoring unparseable ${WORKSPACE_FILE2}`);
25883
25966
  return [];
@@ -26182,10 +26265,10 @@ function editingToolsFor(agent) {
26182
26265
  }
26183
26266
  return EDITING_TOOLS;
26184
26267
  }
26185
- var here = dirname11(fileURLToPath2(import.meta.url));
26268
+ var here = dirname12(fileURLToPath2(import.meta.url));
26186
26269
  function resolveCliVersion() {
26187
26270
  try {
26188
- const pkg = JSON.parse(readFileSync14(join16(here, "..", "..", "package.json"), "utf-8"));
26271
+ const pkg = JSON.parse(readFileSync15(join17(here, "..", "..", "package.json"), "utf-8"));
26189
26272
  return pkg.version ?? "unknown";
26190
26273
  } catch {
26191
26274
  return "unknown";