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

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 (41) hide show
  1. package/README.md +57 -12
  2. package/SKILL.md +5 -2
  3. package/dist/{chunk-UKUAAAXE.js → chunk-27ADT6FQ.js} +2 -2
  4. package/dist/{chunk-OTHKG76J.js → chunk-35FMRWJH.js} +1 -1
  5. package/dist/{chunk-DXJQR4QI.js → chunk-4GHANPWQ.js} +2 -2
  6. package/dist/{chunk-DCJN6LZH.js → chunk-7KABYYWG.js} +747 -430
  7. package/dist/{chunk-LFFLHSQO.js → chunk-CU7RW7XU.js} +63 -6
  8. package/dist/{chunk-H4UNYFUC.js → chunk-DHOQGHKQ.js} +184 -1
  9. package/dist/{chunk-2GP5UFV7.js → chunk-DWRSEC34.js} +5 -3
  10. package/dist/{chunk-36TWVNBE.js → chunk-FVDBULGZ.js} +9 -11
  11. package/dist/{chunk-G2VMA2QO.js → chunk-LY6NJ7KL.js} +6 -4
  12. package/dist/{chunk-ZB4F75SZ.js → chunk-MXVCP33E.js} +5 -1
  13. package/dist/{chunk-TYAHC25F.js → chunk-QCBB5HFX.js} +1 -1
  14. package/dist/{chunk-C3VAA5CG.js → chunk-RK4ZS3HG.js} +2 -2
  15. package/dist/{chunk-ZU563PQI.js → chunk-ROLYLXJS.js} +2 -2
  16. package/dist/{chunk-OH3EZXYX.js → chunk-UO2U6IKF.js} +1 -1
  17. package/dist/{chunk-K7D2EYBS.js → chunk-URLLSALP.js} +2 -2
  18. package/dist/chunk-XEDJ5N3M.js +177 -0
  19. package/dist/{chunk-X64HTHUF.js → chunk-Y5W4TZM7.js} +202 -10
  20. package/dist/{chunk-ICM2XCXS.js → chunk-YYE2NLWX.js} +2 -2
  21. package/dist/{chunk-DSWT6BCC.js → chunk-Z6NPNLOT.js} +825 -665
  22. package/dist/daemon/server.js +836 -540
  23. package/dist/hook-runtime/daemon/server.js +836 -540
  24. package/dist/hook-runtime/hooks/post-commit.js +978 -566
  25. package/dist/hook-runtime/hooks/post-rewrite.js +836 -540
  26. package/dist/hook-runtime/hooks/post-tool-use.js +1215 -796
  27. package/dist/hook-runtime/hooks/pre-tool-use.js +2100 -1741
  28. package/dist/hook-runtime/hooks/prim-hook.js +1312 -861
  29. package/dist/hook-runtime/hooks/session-end.js +232 -134
  30. package/dist/hook-runtime/hooks/session-start.js +1799 -935
  31. package/dist/hook-runtime/index.js +3628 -2461
  32. package/dist/hooks/post-commit.js +14 -10
  33. package/dist/hooks/post-rewrite.js +10 -10
  34. package/dist/hooks/post-tool-use.js +20 -20
  35. package/dist/hooks/pre-commit.js +4 -4
  36. package/dist/hooks/pre-tool-use.js +29 -17
  37. package/dist/hooks/prim-hook.js +22 -18
  38. package/dist/hooks/session-end.js +11 -5
  39. package/dist/hooks/session-start.js +46 -28
  40. package/dist/index.js +623 -390
  41. package/package.json +1 -1
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  gitToplevel
3
- } from "./chunk-H4UNYFUC.js";
3
+ } from "./chunk-DHOQGHKQ.js";
4
4
  import {
5
5
  atomicWriteFile,
6
6
  primConfigDirectory,
7
7
  syncDirectory,
8
8
  syncFile,
9
9
  withFileLockSync
10
- } from "./chunk-2GP5UFV7.js";
10
+ } from "./chunk-DWRSEC34.js";
11
11
 
12
12
  // src/lib/bin-path.ts
13
13
  import { existsSync, readFileSync } from "fs";
@@ -127,6 +127,13 @@ function pinnedHookCommand(bin, args = "") {
127
127
  }
128
128
  var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
129
129
  var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
130
+ var PRIM_CONFIG_SHELL_RESOLVER = [
131
+ 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
132
+ 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
133
+ 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
134
+ '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; ',
135
+ "fi; fi; "
136
+ ].join("");
130
137
  function stableHookCommand(bin, args = "") {
131
138
  if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
132
139
  throw new Error("invalid stable hook command");
@@ -136,11 +143,7 @@ function stableHookCommand(bin, args = "") {
136
143
  // it as inert data during the rolling window so an older uninstall or
137
144
  // reinstall can remove the stable entry without invoking npm or PATH.
138
145
  `prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
139
- 'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
140
- 'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
141
- 'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
142
- '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; ',
143
- "fi; fi; ",
146
+ PRIM_CONFIG_SHELL_RESOLVER,
144
147
  `case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
145
148
  'exec "$prim_launcher" "$@"'
146
149
  ].join("");
@@ -220,6 +223,146 @@ import {
220
223
  writeFileSync
221
224
  } from "fs";
222
225
  import { delimiter, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve } from "path";
226
+
227
+ // src/lib/git-hook-contract.ts
228
+ var MANAGED_GIT_HOOK_NAMES = ["pre-commit", "post-commit", "post-rewrite"];
229
+ var GIT_HOOK_ENTRYPOINT_NAME = "prim-git-hook-v1";
230
+ var GIT_HOOK_CONTRACT_VERSION = 1;
231
+ var CONTRACT_LINE_PREFIX = "# prim git hook v";
232
+ var CONTRACT_LINE_RE = /^# prim git hook v([0-9]+)\b/mu;
233
+ function blockMarkers(hook) {
234
+ return { start: `# >>> prim ${hook} hook >>>`, end: `# <<< prim ${hook} hook <<<` };
235
+ }
236
+ function entrypointCommand(hook) {
237
+ const script = [
238
+ PRIM_CONFIG_SHELL_RESOLVER,
239
+ `case "$prim_config" in /) prim_entry=/${GIT_HOOK_ENTRYPOINT_NAME} ;; `,
240
+ `*) prim_entry="$prim_config/${GIT_HOOK_ENTRYPOINT_NAME}" ;; esac; `,
241
+ '[ -x "$prim_entry" ] || exit 0; exec "$prim_entry" "$@"'
242
+ ].join("");
243
+ return `/bin/sh -c ${shellQuote(script)} ${GIT_HOOK_ENTRYPOINT_NAME} ${hook} "$@"`;
244
+ }
245
+ function managedHookBlock(hook) {
246
+ const { start, end } = blockMarkers(hook);
247
+ const header = `${CONTRACT_LINE_PREFIX}${GIT_HOOK_CONTRACT_VERSION}: a no-op unless prim is installed and enabled here (see \`prim hooks snippet ${hook}\`).`;
248
+ if (hook !== "post-rewrite") {
249
+ return `${start}
250
+ ${header}
251
+ # shellcheck disable=SC2016
252
+ ${entrypointCommand(hook)} || :
253
+ ${end}`;
254
+ }
255
+ return `${start}
256
+ ${header}
257
+ prim_rewrite_stdin=$(mktemp 2>/dev/null) || prim_rewrite_stdin=
258
+ if test -n "\${prim_rewrite_stdin}" && cat >"\${prim_rewrite_stdin}"; then
259
+ # shellcheck disable=SC2016
260
+ ${entrypointCommand(hook)} <"\${prim_rewrite_stdin}" || :
261
+ exec <"\${prim_rewrite_stdin}"
262
+ fi
263
+ if test -n "\${prim_rewrite_stdin}"; then rm -f "\${prim_rewrite_stdin}"; fi
264
+ unset prim_rewrite_stdin
265
+ ${end}`;
266
+ }
267
+ var OPEN_WORDS = /* @__PURE__ */ new Set(["then", "do", "else", "{", "(", "&&", "||", "|"]);
268
+ function canonicalHookBlock(text) {
269
+ const tokens = [];
270
+ let word = "";
271
+ let quote;
272
+ const separate = () => {
273
+ const last = tokens.at(-1);
274
+ if (last !== void 0 && last !== ";" && !OPEN_WORDS.has(last)) tokens.push(";");
275
+ };
276
+ const flush = () => {
277
+ if (word === "") return;
278
+ const last = tokens.at(-1);
279
+ if (last !== void 0 && /^[0-9]*(?:<|>|>>|<&|>&)$/u.test(last))
280
+ tokens[tokens.length - 1] += word;
281
+ else tokens.push(word);
282
+ word = "";
283
+ };
284
+ for (let index = 0; index < text.length; index += 1) {
285
+ const char = text[index];
286
+ if (quote) {
287
+ word += char;
288
+ if (char === quote) quote = void 0;
289
+ continue;
290
+ }
291
+ if (char === "'" || char === '"') {
292
+ quote = char;
293
+ word += char;
294
+ } else if (char === "#" && word === "") {
295
+ const end = text.indexOf("\n", index);
296
+ const comment = text.slice(index, end === -1 ? text.length : end);
297
+ tokens.push(comment.replace(/\s+/gu, " ").trim());
298
+ index = (end === -1 ? text.length : end) - 1;
299
+ } else if (char === "\n" || char === ";") {
300
+ flush();
301
+ if (char === ";" && text[index + 1] === ";") {
302
+ tokens.push(";;");
303
+ index += 1;
304
+ } else {
305
+ separate();
306
+ }
307
+ } else if (/\s/u.test(char)) {
308
+ flush();
309
+ } else {
310
+ word += char;
311
+ }
312
+ }
313
+ flush();
314
+ while (tokens.at(-1) === ";") tokens.pop();
315
+ return tokens.join("\0");
316
+ }
317
+ function hookBlockContractVersion(block) {
318
+ const match = CONTRACT_LINE_RE.exec(block);
319
+ return match?.[1] ? Number(match[1]) : void 0;
320
+ }
321
+ var GIT_HOOK_ENTRYPOINT_CONTENT = `#!/bin/sh
322
+ # prim git hook entrypoint v1 \u2014 managed by prim; do not edit.
323
+ # Run by the "# >>> prim <hook> hook >>>" block in a Git hook file. Acts only
324
+ # where prim is active (git config prim.active true) and never fails the hook.
325
+ case "$0" in */*) prim_dir=\${0%/*} ;; *) exit 0 ;; esac
326
+ prim_launcher="$prim_dir/${STABLE_HOOK_LAUNCHER_NAME}"
327
+ [ -x "$prim_launcher" ] || exit 0
328
+ [ "$(git config --get prim.active 2>/dev/null)" = "true" ] || exit 0
329
+ prim_hook=\${1-}
330
+ if [ "$#" -gt 0 ]; then shift; fi
331
+ case "$prim_hook" in
332
+ pre-commit)
333
+ "$prim_launcher" prim-pre-commit "$@" || :
334
+ ;;
335
+ post-commit)
336
+ prim_sha=$(git rev-parse --verify HEAD 2>/dev/null) || exit 0
337
+ case "$prim_sha" in *[!0-9a-f]* | "") exit 0 ;; esac
338
+ case "\${#prim_sha}" in 40 | 64) ;; *) exit 0 ;; esac
339
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
340
+ prim_observed=$(mktemp "\${TMPDIR:-/tmp}/prim-post-commit-observed.XXXXXXXX" 2>/dev/null) || prim_observed=
341
+ (
342
+ trap '' HUP
343
+ trap 'if [ -n "$prim_observed" ]; then rm -f "$prim_observed"; fi' 0
344
+ PRIM_COMMIT_SHA=$prim_sha PRIM_COMMIT_BRANCH=$prim_branch PRIM_COMMIT_OBSERVED_FILE=$prim_observed "$prim_launcher" prim-post-commit
345
+ ) </dev/null >/dev/null 2>&1 &
346
+ ;;
347
+ post-rewrite)
348
+ case "\${1-}" in amend | rebase) prim_source=$1 ;; *) exit 0 ;; esac
349
+ prim_pairs=$(mktemp "\${TMPDIR:-/tmp}/prim-post-rewrite-pairs.XXXXXXXX" 2>/dev/null) || exit 0
350
+ if ! chmod 600 "$prim_pairs" 2>/dev/null || ! cat >"$prim_pairs"; then
351
+ rm -f "$prim_pairs"
352
+ exit 0
353
+ fi
354
+ prim_branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null) || prim_branch=
355
+ (
356
+ trap '' HUP
357
+ trap 'rm -f "$prim_pairs"' 0
358
+ PRIM_REWRITE_SOURCE=$prim_source PRIM_REWRITE_BRANCH=$prim_branch PRIM_REWRITE_PAIRS_FILE=$prim_pairs "$prim_launcher" prim-post-rewrite
359
+ ) </dev/null >/dev/null 2>&1 &
360
+ ;;
361
+ esac
362
+ exit 0
363
+ `;
364
+
365
+ // src/lib/hook-runtime.ts
223
366
  var HOOK_RUNTIME_SCHEMA_VERSION = 1;
224
367
  var DIRECTORY_MODE = 448;
225
368
  var DATA_FILE_MODE = 384;
@@ -263,6 +406,7 @@ function hookRuntimePaths(options = {}) {
263
406
  return {
264
407
  configDir,
265
408
  launcher: join2(configDir, STABLE_HOOK_LAUNCHER_NAME),
409
+ gitHookEntrypoint: join2(configDir, GIT_HOOK_ENTRYPOINT_NAME),
266
410
  runtimeDir,
267
411
  releasesDir: join2(runtimeDir, "releases"),
268
412
  current: join2(runtimeDir, "current"),
@@ -636,6 +780,13 @@ function stageHookRuntime(options = {}) {
636
780
  mode: LAUNCHER_MODE
637
781
  });
638
782
  }
783
+ const entrypointCurrent = gitHookEntrypointCurrent(paths);
784
+ if (!entrypointCurrent) {
785
+ atomicWriteFile(paths.gitHookEntrypoint, GIT_HOOK_ENTRYPOINT_CONTENT, {
786
+ ensureParent: true,
787
+ mode: LAUNCHER_MODE
788
+ });
789
+ }
639
790
  if (!selectorCurrent) {
640
791
  syncDirectory(paths.releasesDir);
641
792
  atomicWriteFile(paths.current, `${finalRelease.name}
@@ -652,13 +803,38 @@ function stageHookRuntime(options = {}) {
652
803
  now
653
804
  );
654
805
  return {
655
- changed: !launcherCurrent || !selectorCurrent,
806
+ changed: !launcherCurrent || !entrypointCurrent || !selectorCurrent,
656
807
  releaseDir: finalRelease.dir,
657
808
  manifest: finalRelease.manifest,
658
809
  paths
659
810
  };
660
811
  });
661
812
  }
813
+ function gitHookEntrypointCurrent(paths) {
814
+ try {
815
+ return readFileSync2(paths.gitHookEntrypoint, "utf8") === GIT_HOOK_ENTRYPOINT_CONTENT;
816
+ } catch {
817
+ return false;
818
+ }
819
+ }
820
+ function inspectGitHookEntrypoint(options = {}) {
821
+ let paths;
822
+ try {
823
+ paths = hookRuntimePaths(options);
824
+ } catch {
825
+ return "invalid";
826
+ }
827
+ try {
828
+ const entrypoint = lstatIfPresent(paths.gitHookEntrypoint);
829
+ if (!entrypoint) return "missing";
830
+ if (!entrypoint.isFile() || (Number(entrypoint.mode) & 64) === 0 || !gitHookEntrypointCurrent(paths) || readFileSync2(paths.launcher, "utf8") !== STABLE_HOOK_LAUNCHER_CONTENT || !lstatIfPresent(paths.current)?.isFile()) {
831
+ return "invalid";
832
+ }
833
+ return "ready";
834
+ } catch {
835
+ return "invalid";
836
+ }
837
+ }
662
838
  function lstatIfPresent(path) {
663
839
  try {
664
840
  return lstatSync(path);
@@ -669,6 +845,12 @@ function lstatIfPresent(path) {
669
845
  }
670
846
  }
671
847
  function assertOwnedHookRuntime(paths) {
848
+ const entrypoint = lstatIfPresent(paths.gitHookEntrypoint);
849
+ if (entrypoint && (!entrypoint.isFile() || !gitHookEntrypointCurrent(paths))) {
850
+ throw new Error(
851
+ `refusing to remove unrecognized Git hook entrypoint at ${paths.gitHookEntrypoint}`
852
+ );
853
+ }
672
854
  const launcher = lstatIfPresent(paths.launcher);
673
855
  if (launcher) {
674
856
  if (!launcher.isFile() || readFileSync2(paths.launcher, "utf8") !== STABLE_HOOK_LAUNCHER_CONTENT) {
@@ -708,15 +890,18 @@ function assertOwnedHookRuntime(paths) {
708
890
  function removeHookRuntime(options = {}) {
709
891
  const paths = hookRuntimePaths(options);
710
892
  const hadLauncher = lstatIfPresent(paths.launcher) !== void 0;
893
+ const hadEntrypoint = lstatIfPresent(paths.gitHookEntrypoint) !== void 0;
711
894
  const hadRuntime = lstatIfPresent(paths.runtimeDir) !== void 0;
712
- if (!hadLauncher && !hadRuntime) return { changed: false, paths };
895
+ if (!hadLauncher && !hadEntrypoint && !hadRuntime) return { changed: false, paths };
713
896
  assertOwnedHookRuntime(paths);
714
897
  if (!hadRuntime) {
898
+ rmSync(paths.gitHookEntrypoint, { force: true });
715
899
  rmSync(paths.launcher, { force: true });
716
- return { changed: hadLauncher, paths };
900
+ return { changed: true, paths };
717
901
  }
718
902
  withFileLockSync(paths.selectionLock, () => {
719
903
  assertOwnedHookRuntime(paths);
904
+ if (lstatIfPresent(paths.gitHookEntrypoint)) rmSync(paths.gitHookEntrypoint, { force: true });
720
905
  if (lstatIfPresent(paths.launcher)) rmSync(paths.launcher, { force: true });
721
906
  const quarantined = `${paths.runtimeDir}.uninstall-${String(process.pid)}-${randomBytes(8).toString("hex")}`;
722
907
  renameSync(paths.runtimeDir, quarantined);
@@ -1467,10 +1652,17 @@ export {
1467
1652
  stableHookCommand,
1468
1653
  commandMatchesBin,
1469
1654
  hookCommandResolutions,
1655
+ MANAGED_GIT_HOOK_NAMES,
1656
+ GIT_HOOK_CONTRACT_VERSION,
1657
+ blockMarkers,
1658
+ managedHookBlock,
1659
+ canonicalHookBlock,
1660
+ hookBlockContractVersion,
1470
1661
  hookRuntimePaths,
1471
1662
  inspectHookRuntime,
1472
1663
  stableNodePath,
1473
1664
  stageHookRuntime,
1665
+ inspectGitHookEntrypoint,
1474
1666
  assertOwnedHookRuntime,
1475
1667
  removeHookRuntime,
1476
1668
  projectRoot,
@@ -3,10 +3,10 @@ import {
3
3
  } from "./chunk-EZXEWFDG.js";
4
4
  import {
5
5
  scrubEnvironmentPaths
6
- } from "./chunk-K7D2EYBS.js";
6
+ } from "./chunk-URLLSALP.js";
7
7
  import {
8
8
  isValidRepoSyncId
9
- } from "./chunk-DSWT6BCC.js";
9
+ } from "./chunk-DHOQGHKQ.js";
10
10
 
11
11
  // src/hooks/prim-hook-core.ts
12
12
  import { createHash, randomUUID } from "crypto";