@primitive.ai/prim 0.1.0-alpha.61 → 0.1.0-alpha.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -26,15 +26,21 @@ import {
26
26
  import {
27
27
  bindRepository,
28
28
  compareSemver,
29
+ ensureEffectivePostCommitHook,
30
+ ensurePostCommitHookAtPath,
31
+ inspectEffectivePostCommitHook,
32
+ postCommitHookBlock,
29
33
  printJson,
30
- registerSkillCommands
31
- } from "./chunk-N4R7EXZT.js";
34
+ registerSkillCommands,
35
+ uninstallPostCommitHookAtPath,
36
+ uninstallProjectPostCommitHook
37
+ } from "./chunk-LOGW54W5.js";
32
38
  import {
33
39
  fetchFeedbackCapability
34
40
  } from "./chunk-R4CUX6FY.js";
35
41
  import {
36
42
  inspectWorkspaceId
37
- } from "./chunk-IMAIBPUC.js";
43
+ } from "./chunk-UVIWZWM6.js";
38
44
  import {
39
45
  binFile,
40
46
  commandMatchesBin,
@@ -42,16 +48,17 @@ import {
42
48
  packageVersion,
43
49
  pinnedHookCommand,
44
50
  warmBinCache
45
- } from "./chunk-YIMUIPMD.js";
51
+ } from "./chunk-KBFFVYBC.js";
46
52
  import {
47
53
  canonicalGitRoot,
48
54
  canonicalRepositoryPath,
49
55
  decisionIngestionStatus,
50
56
  gitToplevel,
51
57
  isRepoActiveForCapture,
58
+ isValidRepoSyncId,
52
59
  repoSyncId,
53
60
  setRepoActive
54
- } from "./chunk-LDQZASIF.js";
61
+ } from "./chunk-XTNMSS4C.js";
55
62
  import {
56
63
  HttpError,
57
64
  REFRESH_TOKEN_PATH,
@@ -91,17 +98,19 @@ async function applyActivation(active) {
91
98
  }
92
99
  try {
93
100
  let bound;
101
+ let postCommitHook;
94
102
  if (active) {
103
+ postCommitHook = ensureEffectivePostCommitHook(root).path;
95
104
  bound = await bindRepository(root);
96
105
  }
97
106
  setRepoActive(root, active);
98
107
  process.stderr.write(`[prim] prim ${active ? "enabled" : "disabled"} in ${root}
99
108
  `);
100
- printJson({ active, repo: root, ...bound });
109
+ printJson({ active, repo: root, ...bound, ...postCommitHook ? { postCommitHook } : {} });
101
110
  } catch (err) {
102
111
  const detail = err instanceof Error ? err.message : String(err);
103
112
  process.stderr.write(
104
- `[prim] failed to ${active ? "bind and enable" : "disable"} prim: ${detail}
113
+ `[prim] failed to ${active ? "cover, bind, and enable" : "disable"} prim: ${detail}
105
114
  `
106
115
  );
107
116
  process.exit(1);
@@ -3848,6 +3857,46 @@ function checkWorkspaceIdentity() {
3848
3857
  };
3849
3858
  }
3850
3859
  }
3860
+ function classifyRepositoryBinding(value) {
3861
+ return isValidRepoSyncId(value) ? {
3862
+ name: "repo-binding",
3863
+ status: "ok",
3864
+ detail: "valid local repository binding ready"
3865
+ } : {
3866
+ name: "repo-binding",
3867
+ status: "fail",
3868
+ detail: "missing or invalid local prim.repoSyncId \u2014 run `prim enable`"
3869
+ };
3870
+ }
3871
+ function checkRepositoryBinding() {
3872
+ return classifyRepositoryBinding(repoSyncId(process.cwd()));
3873
+ }
3874
+ function classifyPostCommitHook(inspection) {
3875
+ if (inspection.covered) {
3876
+ return {
3877
+ name: "post-commit",
3878
+ status: "ok",
3879
+ detail: `effective and executable \xB7 ${inspection.kind} \xB7 ${inspection.hookPath}`
3880
+ };
3881
+ }
3882
+ return {
3883
+ name: "post-commit",
3884
+ status: "fail",
3885
+ detail: `${inspection.reason ?? "uncovered"} \xB7 ${inspection.hookPath}`
3886
+ };
3887
+ }
3888
+ function checkPostCommitHook() {
3889
+ try {
3890
+ return classifyPostCommitHook(inspectEffectivePostCommitHook());
3891
+ } catch (error) {
3892
+ const detail = error instanceof Error ? error.message : String(error);
3893
+ return {
3894
+ name: "post-commit",
3895
+ status: /not a git repository/iu.test(detail) ? "warn" : "fail",
3896
+ detail: detail.slice(0, 120)
3897
+ };
3898
+ }
3899
+ }
3851
3900
  function checkFeedbackHooks() {
3852
3901
  try {
3853
3902
  const status = performStatus();
@@ -3877,6 +3926,9 @@ function parseMovesStatus(value) {
3877
3926
  if (status.captureState !== "enabled" && status.captureState !== "disabled" || !nullableNumber(status.latestIngestAt) || !nullableNumber(status.latestClassificationAt) || !nullableNumber(status.highWaterMark) || typeof status.pendingSessionCount !== "number" || !Number.isInteger(status.pendingSessionCount) || status.pendingSessionCount < 0 || typeof status.sampled !== "boolean") {
3878
3927
  throw new Error("moves status returned an invalid response");
3879
3928
  }
3929
+ if (status.oldestPendingAt !== void 0 && !nullableNumber(status.oldestPendingAt) || status.oldestPendingAgeMs !== void 0 && !nullableNumber(status.oldestPendingAgeMs) || typeof status.oldestPendingAgeMs === "number" && status.oldestPendingAgeMs < 0 || status.pendingCommitCorrelationCount !== void 0 && (!Number.isInteger(status.pendingCommitCorrelationCount) || status.pendingCommitCorrelationCount < 0)) {
3930
+ throw new Error("moves status returned invalid pending-age fields");
3931
+ }
3880
3932
  return status;
3881
3933
  }
3882
3934
  function classifyMovesStatus(status) {
@@ -3887,10 +3939,11 @@ function classifyMovesStatus(status) {
3887
3939
  };
3888
3940
  let classification;
3889
3941
  if (status.pendingSessionCount > 0) {
3942
+ const oldest = typeof status.oldestPendingAgeMs === "number" ? ` \xB7 oldest ${String(Math.round(status.oldestPendingAgeMs / MS_PER_SECOND))}s` : typeof status.oldestPendingAt === "number" ? ` \xB7 oldest ${String(Math.max(0, Math.round((Date.now() - status.oldestPendingAt) / MS_PER_SECOND)))}s` : "";
3890
3943
  classification = {
3891
3944
  name: "classification",
3892
3945
  status: "warn",
3893
- detail: `${String(status.pendingSessionCount)} session(s) pending${status.sampled ? " in a bounded sample" : ""}`
3946
+ detail: `${String(status.pendingSessionCount)} session(s) pending${oldest}${status.sampled ? " in a bounded sample" : ""}`
3894
3947
  };
3895
3948
  } else if (status.sampled) {
3896
3949
  classification = {
@@ -3905,7 +3958,16 @@ function classifyMovesStatus(status) {
3905
3958
  detail: status.latestClassificationAt === null ? "no sessions awaiting classification" : `caught up \xB7 last ${new Date(status.latestClassificationAt).toISOString()}`
3906
3959
  };
3907
3960
  }
3908
- return [capture, classification];
3961
+ const correlation = status.pendingCommitCorrelationCount === void 0 ? void 0 : status.pendingCommitCorrelationCount > 0 ? {
3962
+ name: "commit-correlation",
3963
+ status: "warn",
3964
+ detail: `${String(status.pendingCommitCorrelationCount)} commit(s) awaiting evidence correlation`
3965
+ } : {
3966
+ name: "commit-correlation",
3967
+ status: "ok",
3968
+ detail: "caught up"
3969
+ };
3970
+ return correlation ? [capture, classification, correlation] : [capture, classification];
3909
3971
  }
3910
3972
  async function checkBackend() {
3911
3973
  try {
@@ -3954,6 +4016,8 @@ async function collectChecks() {
3954
4016
  checkStranded(),
3955
4017
  checkFeedbackHooks(),
3956
4018
  checkWorkspaceIdentity(),
4019
+ checkRepositoryBinding(),
4020
+ checkPostCommitHook(),
3957
4021
  ...backend,
3958
4022
  await checkFeedbackCapability()
3959
4023
  ];
@@ -4337,6 +4401,7 @@ import { Option as Option2 } from "commander";
4337
4401
  var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
4338
4402
  var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
4339
4403
  var HOOKS = [PRE_COMMIT, POST_COMMIT];
4404
+ var GIT_TIMEOUT_MS = 1e3;
4340
4405
  function blockMarkers(spec) {
4341
4406
  return {
4342
4407
  start: `# >>> prim ${spec.hookName} hook >>>`,
@@ -4363,12 +4428,14 @@ ${hookShim(spec.binName)}
4363
4428
  `;
4364
4429
  }
4365
4430
  function huskyBlock(spec) {
4431
+ if (spec.hookName === POST_COMMIT.hookName) return postCommitHookBlock();
4366
4432
  const { start, end } = blockMarkers(spec);
4367
4433
  return `${start}
4368
4434
  ${hookShim(spec.binName)}
4369
4435
  ${end}`;
4370
4436
  }
4371
4437
  function gatedBlock(spec) {
4438
+ if (spec.hookName === POST_COMMIT.hookName) return postCommitHookBlock();
4372
4439
  const { start, end } = blockMarkers(spec);
4373
4440
  return `${start}
4374
4441
  ${gatedShim(spec.binName)}
@@ -4377,6 +4444,21 @@ ${end}`;
4377
4444
  function mergePrimBlock(hookPath, block, binName) {
4378
4445
  if (existsSync7(hookPath)) {
4379
4446
  const existing = readFileSync6(hookPath, "utf-8");
4447
+ const start = block.slice(0, block.indexOf("\n"));
4448
+ const end = block.slice(block.lastIndexOf("\n") + 1);
4449
+ const starts = existing.split(start).length - 1;
4450
+ const ends = existing.split(end).length - 1;
4451
+ if (starts !== 0 || ends !== 0) {
4452
+ if (starts !== 1 || ends !== 1 || existing.indexOf(end) < existing.indexOf(start)) {
4453
+ throw new Error(`malformed Prim hook markers in ${hookPath}`);
4454
+ }
4455
+ const from = existing.indexOf(start);
4456
+ const through = existing.indexOf(end) + end.length;
4457
+ const refreshed = existing.slice(0, from) + block + existing.slice(through);
4458
+ if (refreshed === existing) return false;
4459
+ writeFileSync4(hookPath, refreshed, { mode: 493 });
4460
+ return true;
4461
+ }
4380
4462
  if (containsPrimHook(existing, binName)) return false;
4381
4463
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
4382
4464
  writeFileSync4(hookPath, `${existing}${separator}${block}
@@ -4432,7 +4514,7 @@ function installToHusky(gitRoot, spec = PRE_COMMIT) {
4432
4514
  }
4433
4515
  }
4434
4516
  function installToDotGit(gitRoot, spec = PRE_COMMIT) {
4435
- const hooksDir = resolve2(gitRoot, ".git", "hooks");
4517
+ const hooksDir = projectHooksDir(gitRoot);
4436
4518
  const hookPath = resolve2(hooksDir, spec.hookName);
4437
4519
  if (!existsSync7(hooksDir)) {
4438
4520
  mkdirSync5(hooksDir, { recursive: true });
@@ -4450,6 +4532,18 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
4450
4532
  writeFileSync4(hookPath, dotGitScript(spec), { mode: 493 });
4451
4533
  console.log(`Installed ${spec.hookName} hook at ${hookPath}`);
4452
4534
  }
4535
+ function projectHooksDir(gitRoot) {
4536
+ const commonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
4537
+ cwd: gitRoot,
4538
+ encoding: "utf-8",
4539
+ stdio: ["ignore", "pipe", "ignore"],
4540
+ timeout: GIT_TIMEOUT_MS
4541
+ }).trim();
4542
+ if (commonDir === "") {
4543
+ throw new Error("git returned an empty common directory");
4544
+ }
4545
+ return resolve2(gitRoot, commonDir, "hooks");
4546
+ }
4453
4547
  var PRIM_GIT_HOOKS_DIR = join6(homedir6(), ".config", "prim", "git-hooks");
4454
4548
  function expandTilde(p) {
4455
4549
  return p.startsWith("~/") ? join6(homedir6(), p.slice(2)) : p;
@@ -4461,7 +4555,8 @@ function gitConfigGet(level) {
4461
4555
  try {
4462
4556
  return execFileSync("git", ["config", level, "--get", "core.hooksPath"], {
4463
4557
  encoding: "utf-8",
4464
- stdio: ["ignore", "pipe", "ignore"]
4558
+ stdio: ["ignore", "pipe", "ignore"],
4559
+ timeout: GIT_TIMEOUT_MS
4465
4560
  }).trim();
4466
4561
  } catch {
4467
4562
  return "";
@@ -4489,14 +4584,18 @@ var PASSTHROUGH_HOOKS = [
4489
4584
  ];
4490
4585
  function globalHookScript(spec) {
4491
4586
  const chainExit = spec.hookName === "pre-commit" ? "|| exit $?" : "|| true";
4587
+ const invocation = spec.hookName === POST_COMMIT.hookName ? postCommitHookBlock() : gatedShim(spec.binName);
4588
+ const beforeComments = spec.hookName === POST_COMMIT.hookName ? `${invocation}
4589
+ ` : "";
4590
+ const afterComments = spec.hookName === POST_COMMIT.hookName ? "" : `${invocation}
4591
+ `;
4492
4592
  return `#!/bin/sh
4493
- # prim global ${spec.hookName} hook (core.hooksPath) \u2014 managed by prim; do not edit.
4593
+ ${beforeComments}# prim global ${spec.hookName} hook (core.hooksPath) \u2014 managed by prim; do not edit.
4494
4594
  # Install/uninstall: prim hooks install|uninstall --scope user
4495
4595
  # Runs prim only where activated \u2014 'prim enable' (this repo) or
4496
4596
  # 'git config --global prim.active true' (every repo). Chains to the repo's own
4497
4597
  # hook regardless, so inactive repos are unaffected.
4498
- ${gatedShim(spec.binName)}
4499
- common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
4598
+ ${afterComments}common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
4500
4599
  repo_hook="$common_dir/hooks/${spec.hookName}"
4501
4600
  if [ -x "$repo_hook" ] && ! grep -q '${PRIM_MANAGED_MARK}' "$repo_hook" 2>/dev/null; then
4502
4601
  "$repo_hook" "$@" ${chainExit}
@@ -4521,18 +4620,29 @@ function writeOwnHooks() {
4521
4620
  mkdirSync5(PRIM_GIT_HOOKS_DIR, { recursive: true });
4522
4621
  }
4523
4622
  for (const spec of HOOKS) {
4524
- writeFileSync4(resolve2(PRIM_GIT_HOOKS_DIR, spec.hookName), globalHookScript(spec), {
4525
- mode: 493
4526
- });
4623
+ const path = resolve2(PRIM_GIT_HOOKS_DIR, spec.hookName);
4624
+ if (spec.hookName === POST_COMMIT.hookName) {
4625
+ ensurePostCommitHookAtPath(path, globalHookScript(spec));
4626
+ } else {
4627
+ writeFileSync4(path, globalHookScript(spec), { mode: 493 });
4628
+ }
4527
4629
  }
4528
4630
  for (const name of PASSTHROUGH_HOOKS) {
4529
4631
  writeFileSync4(resolve2(PRIM_GIT_HOOKS_DIR, name), passThroughScript(name), { mode: 493 });
4530
4632
  }
4531
4633
  }
4532
4634
  function appendPrimBlock(hookPath, spec) {
4635
+ if (spec.hookName === POST_COMMIT.hookName) {
4636
+ ensurePostCommitHookAtPath(hookPath);
4637
+ return;
4638
+ }
4533
4639
  mergePrimBlock(hookPath, gatedBlock(spec), spec.binName);
4534
4640
  }
4535
4641
  function stripPrimBlock(hookPath, spec) {
4642
+ if (spec.hookName === POST_COMMIT.hookName) {
4643
+ uninstallPostCommitHookAtPath(hookPath);
4644
+ return;
4645
+ }
4536
4646
  if (!existsSync7(hookPath)) return;
4537
4647
  const existing = readFileSync6(hookPath, "utf-8");
4538
4648
  const primCreated = existing.includes(PRIM_CREATED_MARK);
@@ -4564,7 +4674,10 @@ function installGlobalHooks(opts = {}) {
4564
4674
  );
4565
4675
  }
4566
4676
  writeOwnHooks();
4567
- execFileSync("git", ["config", "--global", "core.hooksPath", PRIM_GIT_HOOKS_DIR]);
4677
+ execFileSync("git", ["config", "--global", "core.hooksPath", PRIM_GIT_HOOKS_DIR], {
4678
+ stdio: ["ignore", "ignore", "pipe"],
4679
+ timeout: GIT_TIMEOUT_MS
4680
+ });
4568
4681
  console.log(
4569
4682
  `Installed prim global git hooks; set core.hooksPath to ${PRIM_GIT_HOOKS_DIR}. Repos are opt-in: run \`prim enable\` in each repo to capture, or \`git config --global prim.active true\` for all.`
4570
4683
  );
@@ -4591,7 +4704,10 @@ function uninstallGlobalHooks() {
4591
4704
  const p = resolve2(PRIM_GIT_HOOKS_DIR, name);
4592
4705
  if (existsSync7(p)) unlinkSync2(p);
4593
4706
  }
4594
- execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"]);
4707
+ execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"], {
4708
+ stdio: ["ignore", "ignore", "pipe"],
4709
+ timeout: GIT_TIMEOUT_MS
4710
+ });
4595
4711
  console.log("Removed prim global git hooks and unset core.hooksPath.");
4596
4712
  return;
4597
4713
  }
@@ -4606,13 +4722,17 @@ function uninstallGlobalHooks() {
4606
4722
  console.log("No prim global git hooks found.");
4607
4723
  }
4608
4724
  function installHooks(gitRoot, target) {
4609
- for (const spec of HOOKS) {
4725
+ for (const spec of HOOKS.filter((candidate) => candidate !== POST_COMMIT)) {
4610
4726
  if (target === "husky") {
4611
4727
  installToHusky(gitRoot, spec);
4612
4728
  } else {
4613
4729
  installToDotGit(gitRoot, spec);
4614
4730
  }
4615
4731
  }
4732
+ const postCommit = ensureEffectivePostCommitHook(gitRoot);
4733
+ console.log(
4734
+ `${postCommit.changed ? "Installed" : "Refreshed"} effective post-commit hook at ${postCommit.path}.`
4735
+ );
4616
4736
  }
4617
4737
  function registerHooksCommands(program2) {
4618
4738
  const hooks = program2.command("hooks").description("Manage git hooks");
@@ -4674,8 +4794,9 @@ function registerHooksCommands(program2) {
4674
4794
  return;
4675
4795
  }
4676
4796
  const gitRoot = getGitRoot();
4677
- for (const spec of HOOKS) {
4678
- const hookPath = resolve2(gitRoot, ".git", "hooks", spec.hookName);
4797
+ const hooksDir = projectHooksDir(gitRoot);
4798
+ for (const spec of HOOKS.filter((candidate) => candidate !== POST_COMMIT)) {
4799
+ const hookPath = resolve2(hooksDir, spec.hookName);
4679
4800
  if (!existsSync7(hookPath)) {
4680
4801
  console.log(`No ${spec.hookName} hook found.`);
4681
4802
  continue;
@@ -4687,6 +4808,14 @@ function registerHooksCommands(program2) {
4687
4808
  console.log(`Left ${spec.hookName} hook at ${hookPath} untouched (not a prim hook).`);
4688
4809
  }
4689
4810
  }
4811
+ const postCommit = uninstallProjectPostCommitHook(gitRoot);
4812
+ if (!postCommit.changed) {
4813
+ console.log(`No Prim post-commit block found at ${postCommit.path}.`);
4814
+ } else if (postCommit.removedFile) {
4815
+ console.log(`Removed Prim-created post-commit hook at ${postCommit.path}.`);
4816
+ } else {
4817
+ console.log(`Removed the Prim post-commit block from ${postCommit.path}.`);
4818
+ }
4690
4819
  });
4691
4820
  }
4692
4821
 
@@ -5130,12 +5259,6 @@ function planSetupSteps(opts) {
5130
5259
  args: ["daemon", "start"],
5131
5260
  required: true
5132
5261
  });
5133
- steps.push({
5134
- key: "health",
5135
- label: "Capture health",
5136
- args: ["doctor"],
5137
- required: true
5138
- });
5139
5262
  } else {
5140
5263
  steps.push({
5141
5264
  key: "daemon-opt-out",
@@ -5154,10 +5277,18 @@ function planSetupSteps(opts) {
5154
5277
  steps.push({ key: "skill", label: "Agent skill", args: skillArgs, required: true });
5155
5278
  steps.push({
5156
5279
  key: "enable",
5157
- label: "Bind and activate this repo",
5280
+ label: "Activate this repo",
5158
5281
  args: ["enable"],
5159
5282
  required: true
5160
5283
  });
5284
+ if (opts.daemon) {
5285
+ steps.push({
5286
+ key: "health",
5287
+ label: "Capture health",
5288
+ args: ["doctor"],
5289
+ required: true
5290
+ });
5291
+ }
5161
5292
  return steps;
5162
5293
  }
5163
5294
  var CONFLICT_SESSION = "session";
@@ -5295,7 +5426,10 @@ function registerSetupCommand(program2, dependencies = {}) {
5295
5426
  note("pre-authorize \xB7 writing prim allow-rule (user scope)\u2026");
5296
5427
  results.preauth = run(["claude", "preauth", "--scope", "user"]).code === 0 ? "ok" : "skipped";
5297
5428
  }
5298
- for (const step of planSetupSteps({ agent, daemon: opts.daemon, scope })) {
5429
+ const setupSteps = planSetupSteps({ agent, daemon: opts.daemon, scope });
5430
+ for (const step of setupSteps.filter(
5431
+ (candidate) => candidate.key !== "enable" && candidate.key !== "health"
5432
+ )) {
5299
5433
  note(`${step.label} \xB7 installing\u2026`);
5300
5434
  const { code } = run(step.args);
5301
5435
  results[step.key] = code === 0 ? "ok" : step.required ? "failed" : "skipped";
@@ -5312,6 +5446,18 @@ function registerSetupCommand(program2, dependencies = {}) {
5312
5446
  );
5313
5447
  }
5314
5448
  }
5449
+ const enableStep = setupSteps.find((candidate) => candidate.key === "enable");
5450
+ if (enableStep) {
5451
+ note(`${enableStep.label} \xB7 installing\u2026`);
5452
+ const { code } = run(enableStep.args);
5453
+ results[enableStep.key] = code === 0 ? "ok" : enableStep.required ? "failed" : "skipped";
5454
+ }
5455
+ const healthStep = setupSteps.find((candidate) => candidate.key === "health");
5456
+ if (healthStep) {
5457
+ note(`${healthStep.label} \xB7 verifying\u2026`);
5458
+ const { code } = run(healthStep.args);
5459
+ results[healthStep.key] = code === 0 ? "ok" : healthStep.required ? "failed" : "skipped";
5460
+ }
5315
5461
  note("welcome");
5316
5462
  results.welcome = run(["welcome", "--agent", agent]).code === 0 ? "ok" : "failed";
5317
5463
  const failed = Object.entries(results).filter(([, v]) => v === "failed").map(([k]) => k);
@@ -140,14 +140,17 @@ import { join as join4 } from "path";
140
140
 
141
141
  // src/lib/git.ts
142
142
  import { execFileSync } from "child_process";
143
+ import { createHash as createHash2 } from "crypto";
143
144
  import { lstatSync, realpathSync } from "fs";
144
145
  import { basename, dirname as dirname3, isAbsolute, relative, resolve as resolve2, sep } from "path";
146
+ var GIT_TIMEOUT_MS = 1e3;
145
147
  function gitToplevel(cwd) {
146
148
  try {
147
149
  return execFileSync("git", ["rev-parse", "--show-toplevel"], {
148
150
  cwd,
149
151
  encoding: "utf-8",
150
- stdio: ["ignore", "pipe", "ignore"]
152
+ stdio: ["ignore", "pipe", "ignore"],
153
+ timeout: GIT_TIMEOUT_MS
151
154
  }).trim();
152
155
  } catch {
153
156
  return null;
@@ -156,6 +159,7 @@ function gitToplevel(cwd) {
156
159
 
157
160
  // src/lib/activation.ts
158
161
  var PRIM_ACTIVE_KEY = "prim.active";
162
+ var GIT_TIMEOUT_MS2 = 1e3;
159
163
  var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
160
164
  var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
161
165
  function repoActiveFlag(cwd) {
@@ -163,7 +167,8 @@ function repoActiveFlag(cwd) {
163
167
  const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
164
168
  cwd,
165
169
  encoding: "utf-8",
166
- stdio: ["ignore", "pipe", "ignore"]
170
+ stdio: ["ignore", "pipe", "ignore"],
171
+ timeout: GIT_TIMEOUT_MS2
167
172
  }).trim();
168
173
  if (value === "true") return "true";
169
174
  if (value === "false") return "false";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.61",
3
+ "version": "0.1.0-alpha.62",
4
4
  "description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,84 +0,0 @@
1
- // src/hooks/redact.ts
2
- import { existsSync, readFileSync } from "fs";
3
- import { join } from "path";
4
- var DEFAULT_RULES = [
5
- { pattern: /Bearer\s+[A-Za-z0-9._\-]+/g, reason: "bearer-token" },
6
- { pattern: /sk-[A-Za-z0-9]{32,}/g, reason: "sk-api-key" },
7
- {
8
- pattern: /-----BEGIN [A-Z ]+PRIVATE KEY-----[\s\S]+?-----END [A-Z ]+PRIVATE KEY-----/g,
9
- reason: "private-key"
10
- },
11
- { pattern: /xox[abprs]-[A-Za-z0-9-]{10,}/g, reason: "slack-token" },
12
- { pattern: /ghp_[A-Za-z0-9]{36,}/g, reason: "github-pat" }
13
- ];
14
- var MAX_SCRUB_LEN = 256e3;
15
- var WORKSPACE_FILE = ".prim/redaction.json";
16
- function debugRedaction(message, err) {
17
- if (!process.env.PRIM_HOOK_DEBUG) {
18
- return;
19
- }
20
- const detail = err instanceof Error ? err.message : String(err);
21
- process.stderr.write(`[prim-hook] redaction: ${message}: ${detail}
22
- `);
23
- }
24
- function loadWorkspaceRules(cwd) {
25
- const path = join(cwd, WORKSPACE_FILE);
26
- if (!existsSync(path)) {
27
- return [];
28
- }
29
- let cfg;
30
- try {
31
- cfg = JSON.parse(readFileSync(path, "utf-8"));
32
- } catch (err) {
33
- debugRedaction(`ignoring unparseable ${WORKSPACE_FILE}`, err);
34
- return [];
35
- }
36
- if (!cfg.rules) {
37
- return [];
38
- }
39
- const compiled = [];
40
- for (const r of cfg.rules) {
41
- try {
42
- compiled.push({ pattern: new RegExp(r.pattern, r.flags ?? "g"), reason: r.reason });
43
- } catch (err) {
44
- debugRedaction(`skipping invalid redaction rule "${r.reason}"`, err);
45
- }
46
- }
47
- return compiled;
48
- }
49
- function applyRules(input, rules) {
50
- if (input.length > MAX_SCRUB_LEN) {
51
- return "<REDACTED:oversized>";
52
- }
53
- let out = input;
54
- for (const rule of rules) {
55
- out = out.replace(rule.pattern, `<REDACTED:${rule.reason}>`);
56
- }
57
- return out;
58
- }
59
- function scrub(value, rules = DEFAULT_RULES) {
60
- if (typeof value === "string") {
61
- return applyRules(value, rules);
62
- }
63
- if (Array.isArray(value)) {
64
- return value.map((v) => scrub(v, rules));
65
- }
66
- if (value && typeof value === "object") {
67
- const out = {};
68
- for (const [k, v] of Object.entries(value)) {
69
- out[k] = scrub(v, rules);
70
- }
71
- return out;
72
- }
73
- return value;
74
- }
75
- function scrubFromCwd(value, cwd) {
76
- const rules = [...DEFAULT_RULES, ...loadWorkspaceRules(cwd)];
77
- return scrub(value, rules);
78
- }
79
-
80
- export {
81
- DEFAULT_RULES,
82
- scrub,
83
- scrubFromCwd
84
- };