@primitive.ai/prim 0.1.0-alpha.35 → 0.1.0-alpha.37

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/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # @primitive.ai/prim
2
2
 
3
3
  The official CLI for [Primitive](https://getprimitive.ai)'s **decision graph**. It
4
- passively captures the decisions your team makes while coding, gates edits that
5
- conflict with prior team decisions, and reports team presence from the command
6
- line and via session + git hooks.
4
+ passively captures the decisions your team makes while coding and reports team
5
+ presence from the command line and via session + git hooks.
7
6
 
8
7
  > [!WARNING]
9
8
  > This project is in **alpha**. Commands and APIs may change between releases.
10
9
 
10
+ > [!NOTE]
11
+ > **Conflict Gates** check each edit against the decision graph and surface any
12
+ > load-bearing decision it conflicts with; their **Enforcement** tier blocks or pauses
13
+ > a conflicting edit until you reconcile and retry. Conflict Gates are **not currently
14
+ > enabled** — automatic decision capture runs regardless. To enable them for your team,
15
+ > contact **support@getprimitive.ai**.
16
+
11
17
  ## Installation
12
18
 
13
19
  Requires Node.js 20+.
@@ -37,7 +43,7 @@ Or run the steps individually:
37
43
  # 1. Authenticate via browser (WorkOS OAuth)
38
44
  prim auth login
39
45
 
40
- # 2. Wire the session hooks (decision capture + conflict gate + presence)
46
+ # 2. Wire the session hooks (decision capture + presence)
41
47
  prim claude install # or: prim codex install / prim hermes install
42
48
 
43
49
  # 3. Start the companion daemon (latency + team presence)
@@ -94,7 +100,7 @@ prim auth status # Check authentication status
94
100
  ### Session integration
95
101
 
96
102
  Wires the agent's session hooks so the decisions you make are captured into the
97
- graph, conflicting edits are gated, and presence is reported. Each hook
103
+ graph, and presence is reported. Each hook
98
104
  self-resolves the CLI at run time (PATH, then a local install, then
99
105
  `npx --yes @latest`), so it keeps working with no global install.
100
106
 
@@ -147,9 +153,12 @@ would create a cycle (exit 2); an unresolved id exits 4.
147
153
  ### Reconcile
148
154
 
149
155
  ```bash
150
- prim reconcile <id> # Mint a single-use bypass for a decision a gate flagged
156
+ prim reconcile <id> # Mint a single-use bypass for a decision Conflict Gates flagged
151
157
  ```
152
158
 
159
+ Part of Conflict Gates **Enforcement**, which is not currently enabled (see the note
160
+ at the top). Contact support@getprimitive.ai to enable it.
161
+
153
162
  ### Hooks
154
163
 
155
164
  ```bash
package/SKILL.md CHANGED
@@ -9,9 +9,9 @@ description: Use the prim CLI for Primitive's decision graph — passive decisio
9
9
 
10
10
  ## Mental model
11
11
 
12
- As your team codes, prim passively captures the **decisions** you make -- which library, which pattern, which config value -- into a queryable graph, and links them: a decision can depend on earlier decisions (auto-linked from shared files, or related by hand — see *Relate decisions*) and reference the files it touched. When a later change conflicts with a load-bearing prior decision, prim **gates** the edit and surfaces the decision for review.
12
+ As your team codes, prim passively captures the **decisions** you make -- which library, which pattern, which config value -- into a queryable graph, and links them: a decision can depend on earlier decisions (auto-linked from shared files, or related by hand — see *Relate decisions*) and reference the files it touched. **Conflict Gates** can check a later change against that graph and surface any load-bearing decision it conflicts with; with **Enforcement**, prim blocks the edit until you reconcile the decision and retry. Conflict Gates are **not currently enabled** — capture runs regardless. To enable them for your team, contact support@getprimitive.ai.
13
13
 
14
- You never invoke capture. It runs automatically through the session hooks installed by `npx --yes @primitive.ai/prim claude install` (Claude Code), `npx --yes @primitive.ai/prim codex install` (Codex), or `npx --yes @primitive.ai/prim hermes install` (Hermes). Your job is to **respond** to the gate, **read** the graph before load-bearing edits, and **answer** the occasional rationale confirmation.
14
+ You never invoke capture. It runs automatically through the session hooks installed by `npx --yes @primitive.ai/prim claude install` (Claude Code), `npx --yes @primitive.ai/prim codex install` (Codex), or `npx --yes @primitive.ai/prim hermes install` (Hermes). Your job is to **read** the graph before load-bearing edits and **answer** the occasional rationale confirmation. (Responding to Conflict Gates applies only once Enforcement is enabled — see below.)
15
15
 
16
16
  ## Auth
17
17
 
@@ -29,17 +29,19 @@ The CLI auto-refreshes a still-valid session from the stored refresh token (proa
29
29
 
30
30
  1. Every command accepts `--help`. When unsure of flags, run `npx --yes @primitive.ai/prim <cmd> --help` rather than guessing.
31
31
  2. The CLI prints API errors as one-liners to stderr and exits non-zero. Treat any non-zero exit as actionable. If auth-related, re-check `auth status`.
32
- 3. `<idOrShortId>` arguments accept either a full decision ID or the short ID shown in feeds and gate reasons.
32
+ 3. `<idOrShortId>` arguments accept either a full decision ID or the short ID shown in feeds (and gate reasons, when Conflict Gates are enabled).
33
33
 
34
- ## Heed the conflict gate
34
+ ## Conflict Gates & Enforcement (not currently enabled)
35
35
 
36
- Before an edit (Claude Code: Edit/Write/MultiEdit; Codex: apply_patch; Hermes: write_file/patch) a PreToolUse hook scores the target file against the graph:
36
+ **Conflict Gates** check each edit against the decision graph before it lands and surface any load-bearing decision it conflicts with. Their **Enforcement** tier goes further -- a conflicting edit is blocked (or paused for confirmation) until you reconcile the decision and retry. **Conflict Gates are not currently enabled** — automatic decision capture (above) runs regardless. To enable Conflict Gates and Enforcement for your team, contact support@getprimitive.ai.
37
+
38
+ When Enforcement is enabled, before an edit (Claude Code: Edit/Write/MultiEdit; Codex: apply_patch; Hermes: write_file/patch) a PreToolUse hook scores the target file against the graph:
37
39
 
38
40
  - **deny** -- the edit is blocked: it conflicts with a load-bearing prior decision. Don't fight it. Read the reason line; it names the decision id. If you genuinely intend to override that decision, run `npx --yes @primitive.ai/prim reconcile dec_<shortId>`, then retry the edit once. Otherwise choose an approach that respects the decision.
39
41
  - **warn / additional context** -- the edit proceeds, but a relevant prior decision is surfaced. Read it. On Codex a would-be `ask` is delivered as allow-plus-context (Codex can't pause mid-tool), so that context is your only signal -- read it before continuing. Hermes has no soft-confirm tier, so a would-be `ask` arrives as a **deny** carrying the same reconcile directive: reconcile and retry, or set `PRIM_HOOK_MODE=warn` to downgrade it to context-only.
40
42
  - **"decision check skipped / not verified" or "... partial / truncated"** -- the check could not fully run. Treat constraints as UNKNOWN, not clear; never read silence as approval.
41
43
 
42
- The gate fail-opens on its *own* infrastructure errors (no daemon, network blip, org-unbound token) -- a setup problem never blocks your edit. That is exactly why an "unavailable" note matters: it is the honest signal that the check, not your edit, is what failed.
44
+ When enabled, the gate fail-opens on its *own* infrastructure errors (no daemon, network blip, org-unbound token) -- a setup problem would not block your edit. That is why an "unavailable" note would matter: it is the honest signal that the check, not your edit, is what failed.
43
45
 
44
46
  ## Read the graph before large or load-bearing edits
45
47
 
@@ -49,6 +51,8 @@ The gate fail-opens on its *own* infrastructure errors (no daemon, network blip,
49
51
 
50
52
  ## Reconcile and the verdict footer
51
53
 
54
+ Reconcile and the verdict footer are part of Conflict Gates **Enforcement**, which is **not currently enabled** (contact support@getprimitive.ai to turn it on); the `reconcile` command stays available regardless. When Enforcement is on:
55
+
52
56
  `npx --yes @primitive.ai/prim reconcile <idOrShortId>` mints a single-use bypass for the named decision -- it prints `[prim] reconcile bypass issued for dec_<short> (expires in ...)` to STDERR, with the bypass JSON on STDOUT. Your *next* edit to the governed file then goes through, and on that edit prim prints a verdict footer to STDERR -- confirmation the override was recorded, not silently dropped:
53
57
 
54
58
  ```
@@ -75,7 +79,7 @@ Capture is automatic for the decisions you *make while coding*. When the user in
75
79
  npx --yes @primitive.ai/prim decisions create --intent "Adopt prosemirror-collab over Yjs" --area data --rationale "Server-authoritative ordering" --alternatives "Yjs,Automerge"
76
80
  ```
77
81
 
78
- Only `--intent` is required. Optional: `--kind` (change|exploration|task_execution|unclear, default change), `--rationale`, `--area`, `--decided`, `--alternatives` (comma-separated), `--confidence` (high|medium|low, default high), `--reversibility` (high|low, default high), and `--files` (comma-separated repo-relative paths the decision governs — pass these to make the conflict gate fire on later edits to those files, same path form as `decisions check`). STDOUT is the created identity `{ decisionId, shortId, createdAt }`; STDERR prints `[prim] created dec_<short>.` — pass that `dec_<short>` straight into `decisions show` / `cascade` / `confirm`. Author on the user's behalf only when they ask for a decision to be recorded; don't narrate your own routine edits into the graph (the hooks already do that).
82
+ Only `--intent` is required. Optional: `--kind` (change|exploration|task_execution|unclear, default change), `--rationale`, `--area`, `--decided`, `--alternatives` (comma-separated), `--confidence` (high|medium|low, default high), `--reversibility` (high|low, default high), and `--files` (comma-separated repo-relative paths the decision governs — these are the files Conflict Gates would check on later edits, same path form as `decisions check`; Conflict Gates are not currently enabled). STDOUT is the created identity `{ decisionId, shortId, createdAt }`; STDERR prints `[prim] created dec_<short>.` — pass that `dec_<short>` straight into `decisions show` / `cascade` / `confirm`. Author on the user's behalf only when they ask for a decision to be recorded; don't narrate your own routine edits into the graph (the hooks already do that).
79
83
 
80
84
  ## Relate decisions (link / unlink)
81
85
 
@@ -116,14 +120,14 @@ npx --yes @primitive.ai/prim hooks uninstall
116
120
 
117
121
  Under `CI=1` (or with `--non-interactive`), `hooks install` fails fast in a Husky repo unless `--yes` or `--target` is set; the error names both escapes. `hooks uninstall` only removes the `.git/hooks` copies — if a hook was installed into `.husky/`, remove the prim block from that file manually. To suppress the hooks for one commit, use `git commit --no-verify`.
118
122
 
119
- These git hooks are separate from the **session hooks** (`claude install` / `codex install` / `hermes install`) that drive in-session capture and the conflict gate.
123
+ These git hooks are separate from the **session hooks** (`claude install` / `codex install` / `hermes install`) that drive in-session capture (and Conflict Gates, when enabled).
120
124
 
121
125
  ## Output formats
122
126
 
123
127
  The CLI keeps STDOUT machine-readable and STDERR human-readable. The `decisions` and `reconcile` commands **always** emit a single JSON document on STDOUT — no flag needed; pipe straight to `jq`. The `decisions` commands have **no** `--json` flag and reject one; `reconcile` accepts a reserved no-op `--json`. `auth status` and `skill status` default to human-readable STDOUT and take `--json` to switch to JSON.
124
128
 
125
129
  - **STDOUT is machine-readable** — JSON (one document per invocation). `decisions` reads project lean shapes, not raw rows.
126
- - **STDERR is human-readable** — a verdict-first line, plus the gate/verdict-footer/presence notes.
130
+ - **STDERR is human-readable** — a verdict-first line, plus presence notes (and, when Conflict Gates are enabled, the gate/verdict-footer notes).
127
131
  - **Exit code is authoritative** where it carries meaning — `auth status` exits 0 when authenticated; `decisions show`/`cascade`/`confirm` exit non-zero (e.g. 4 not-found) on a missing or unauthorized id.
128
132
 
129
133
  Examples:
@@ -135,12 +139,12 @@ Examples:
135
139
 
136
140
  ## Pitfalls
137
141
 
138
- - **An "unavailable" / "not verified" gate or check is not an all-clear.** Treat constraints as UNKNOWN and proceed deliberately; never read the silence as approval.
139
- - **A `deny` means a real prior decision conflicts.** Reconcile only when you genuinely intend to override it; otherwise pick an approach that respects it.
142
+ - **An "unavailable" / "not verified" decision check is not an all-clear.** Treat constraints as UNKNOWN and proceed deliberately; never read the silence as approval — the same holds for Conflict Gates once enabled.
143
+ - **When Enforcement is enabled, a `deny` means a real prior decision conflicts.** Reconcile only when you genuinely intend to override it; otherwise pick an approach that respects it.
140
144
  - **Reconcile bypasses are single-use and short-lived.** One bypass clears your *next* edit to the governed file; it is not a standing override.
141
145
  - **Capture of your coding activity is automatic, never manual.** If decisions aren't showing up, check that the session hooks are installed (`claude status` / `codex status` / `hermes status`) and the daemon is running — don't try to inject moves by hand. (Deliberately *authoring* a decision the user asks you to record is a separate, supported path — `decisions create`, above.)
142
146
  - **Don't fabricate rationale on a confirmation.** If you don't know why a decision was made, say so rather than guessing.
143
147
 
144
148
  ## After each task
145
149
 
146
- If the conflict gate denied or warned you, report which decision(s) it named and whether you reconciled. If you read the graph before a load-bearing change, note what you found so the user can verify in the dashboard.
150
+ If Conflict Gates are enabled and one denied or warned you, report which decision(s) it named and whether you reconciled. If you read the graph before a load-bearing change, note what you found so the user can verify in the dashboard.
@@ -0,0 +1,113 @@
1
+ // src/lib/bin-cache.ts
2
+ import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join as join2 } from "path";
5
+
6
+ // src/lib/bin-path.ts
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { dirname, isAbsolute, join } from "path";
9
+ import { fileURLToPath } from "url";
10
+ var PKG_NAME = "@primitive.ai/prim";
11
+ var ROOT_WALK_LIMIT = 6;
12
+ var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
13
+ var BIN_CACHE_DIR_SH = "${XDG_CACHE_HOME:-$HOME/.cache}/prim/bin";
14
+ var BIN_CACHE_TTL_MIN_DEFAULT = 1440;
15
+ var resolvedRoot;
16
+ function locateRoot() {
17
+ if (resolvedRoot !== void 0) {
18
+ return resolvedRoot;
19
+ }
20
+ let dir = dirname(fileURLToPath(import.meta.url));
21
+ for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
22
+ const manifestPath = join(dir, "package.json");
23
+ if (existsSync(manifestPath)) {
24
+ try {
25
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
26
+ if (manifest.name === PKG_NAME && manifest.bin) {
27
+ resolvedRoot = { dir, bin: manifest.bin };
28
+ return resolvedRoot;
29
+ }
30
+ } catch {
31
+ }
32
+ }
33
+ const parent = dirname(dir);
34
+ if (parent === dir) {
35
+ break;
36
+ }
37
+ dir = parent;
38
+ }
39
+ resolvedRoot = null;
40
+ return resolvedRoot;
41
+ }
42
+ function binFile(bin) {
43
+ const root = locateRoot();
44
+ const rel = root?.bin[bin];
45
+ if (!root || !rel) {
46
+ return null;
47
+ }
48
+ return isAbsolute(rel) ? rel : join(root.dir, rel);
49
+ }
50
+ function hookShimCommand(bin, args = "", opts = {}) {
51
+ const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
52
+ const ladder = `if command -v ${bin} >/dev/null 2>&1; then ${invoke(bin)}; elif [ -f "./node_modules/.bin/${bin}" ]; then ${invoke(`./node_modules/.bin/${bin}`)}; else ${invoke(`${NPX_FALLBACK} ${bin}`)}; fi`;
53
+ if (opts.cacheRead === false) {
54
+ return ladder;
55
+ }
56
+ const execArgs = args ? ` ${args}` : "";
57
+ const cacheBranch = `d="${BIN_CACHE_DIR_SH}"; if [ "\${PRIM_BIN_CACHE:-1}" != "0" ] && [ -f "$d/${bin}" ] && [ -f "$d/node" ] && [ -n "$(find "$d/${bin}" -mmin "-\${PRIM_BIN_CACHE_TTL_MIN:-${BIN_CACHE_TTL_MIN_DEFAULT}}" 2>/dev/null)" ]; then n=$(cat "$d/node"); p=$(cat "$d/${bin}"); if [ -x "$n" ] && [ -f "$p" ]; then export PRIM_BIN_CACHE_HIT=1; exec "$n" "$p"${execArgs}; fi; fi; `;
58
+ return cacheBranch + ladder;
59
+ }
60
+ function detachedHookShimCommand(bin, args = "") {
61
+ return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${hookShimCommand(bin, args, { cacheRead: false })}; }; } </dev/null >/dev/null 2>&1 &`;
62
+ }
63
+ function commandMatchesBin(command, bin) {
64
+ if (!command) {
65
+ return false;
66
+ }
67
+ const c = command.trim();
68
+ if (c === bin || c.startsWith(`${bin} `)) {
69
+ return true;
70
+ }
71
+ return c.includes(`command -v ${bin} `);
72
+ }
73
+
74
+ // src/lib/bin-cache.ts
75
+ var CACHED_BINS = ["prim", "prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
76
+ function binCacheDir() {
77
+ const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
78
+ return join2(base, "prim", "bin");
79
+ }
80
+ function writeAtomic(path, content) {
81
+ const tmp = `${path}.${process.pid}.tmp`;
82
+ writeFileSync(tmp, content, { mode: 384 });
83
+ renameSync(tmp, path);
84
+ }
85
+ function warmBinCache() {
86
+ try {
87
+ if (process.env.PRIM_BIN_CACHE_HIT) {
88
+ return;
89
+ }
90
+ if (process.env.PRIM_BIN_CACHE === "0") {
91
+ return;
92
+ }
93
+ const dir = binCacheDir();
94
+ mkdirSync(dir, { recursive: true, mode: 448 });
95
+ chmodSync(dir, 448);
96
+ writeAtomic(join2(dir, "node"), process.execPath);
97
+ for (const bin of CACHED_BINS) {
98
+ const file = binFile(bin);
99
+ if (file) {
100
+ writeAtomic(join2(dir, bin), file);
101
+ }
102
+ }
103
+ } catch {
104
+ }
105
+ }
106
+
107
+ export {
108
+ binFile,
109
+ hookShimCommand,
110
+ detachedHookShimCommand,
111
+ commandMatchesBin,
112
+ warmBinCache
113
+ };
@@ -12,6 +12,9 @@ import {
12
12
  import {
13
13
  isRepoActiveForCapture
14
14
  } from "../chunk-LUPD2JSH.js";
15
+ import {
16
+ warmBinCache
17
+ } from "../chunk-HSZPTVKU.js";
15
18
  import {
16
19
  getClient
17
20
  } from "../chunk-26VA3ADF.js";
@@ -100,6 +103,7 @@ async function ingestMove(move) {
100
103
  );
101
104
  }
102
105
  async function main() {
106
+ warmBinCache();
103
107
  const agent = parseAgent(process.argv);
104
108
  let raw;
105
109
  try {
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  isRepoActiveForCapture
4
4
  } from "../chunk-LUPD2JSH.js";
5
+ import {
6
+ warmBinCache
7
+ } from "../chunk-HSZPTVKU.js";
5
8
  import {
6
9
  getClient,
7
10
  getSiteUrl
@@ -262,6 +265,7 @@ async function checkOneFile(file) {
262
265
  );
263
266
  }
264
267
  async function main() {
268
+ warmBinCache();
265
269
  let raw;
266
270
  try {
267
271
  raw = await readStdin();
@@ -13,6 +13,9 @@ import {
13
13
  import {
14
14
  isRepoActiveForCapture
15
15
  } from "../chunk-LUPD2JSH.js";
16
+ import {
17
+ warmBinCache
18
+ } from "../chunk-HSZPTVKU.js";
16
19
  import "../chunk-26VA3ADF.js";
17
20
  import {
18
21
  normalizeEnvelope,
@@ -41,6 +44,7 @@ function spawnBackgroundFlush() {
41
44
  }).unref();
42
45
  }
43
46
  try {
47
+ warmBinCache();
44
48
  const agent = parseAgent(process.argv);
45
49
  const raw = readFileSync(0, "utf-8");
46
50
  const parsed = normalizeEnvelope(JSON.parse(raw), agent);
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ warmBinCache
4
+ } from "../chunk-HSZPTVKU.js";
2
5
  import {
3
6
  getSiteUrl
4
7
  } from "../chunk-26VA3ADF.js";
@@ -45,6 +48,7 @@ function emit(additionalContext) {
45
48
  `);
46
49
  }
47
50
  async function main() {
51
+ warmBinCache();
48
52
  const agent = parseAgent(process.argv);
49
53
  let raw;
50
54
  try {
package/dist/index.js CHANGED
@@ -25,6 +25,13 @@ import {
25
25
  gitToplevel,
26
26
  setRepoActive
27
27
  } from "./chunk-LUPD2JSH.js";
28
+ import {
29
+ binFile,
30
+ commandMatchesBin,
31
+ detachedHookShimCommand,
32
+ hookShimCommand,
33
+ warmBinCache
34
+ } from "./chunk-HSZPTVKU.js";
28
35
  import {
29
36
  HttpError,
30
37
  REFRESH_TOKEN_PATH,
@@ -42,9 +49,9 @@ import {
42
49
  } from "./chunk-UTKQTZHL.js";
43
50
 
44
51
  // src/index.ts
45
- import { readFileSync as readFileSync12 } from "fs";
46
- import { dirname as dirname9, resolve as resolve5 } from "path";
47
- import { fileURLToPath as fileURLToPath5 } from "url";
52
+ import { readFileSync as readFileSync11 } from "fs";
53
+ import { dirname as dirname8, resolve as resolve5 } from "path";
54
+ import { fileURLToPath as fileURLToPath4 } from "url";
48
55
  import { Command } from "commander";
49
56
  import updateNotifier from "update-notifier";
50
57
 
@@ -529,78 +536,16 @@ async function exchangeCode(siteUrl, code, codeVerifier, redirectUri) {
529
536
  // src/commands/claude-install.ts
530
537
  import {
531
538
  closeSync,
532
- existsSync as existsSync3,
539
+ existsSync as existsSync2,
533
540
  fsyncSync,
534
541
  mkdirSync as mkdirSync2,
535
542
  openSync,
536
- readFileSync as readFileSync3,
543
+ readFileSync as readFileSync2,
537
544
  renameSync,
538
545
  writeFileSync as writeFileSync2
539
546
  } from "fs";
540
547
  import { homedir } from "os";
541
- import { dirname as dirname3, join as join2 } from "path";
542
-
543
- // src/lib/bin-path.ts
544
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
545
- import { dirname as dirname2, isAbsolute, join } from "path";
546
- import { fileURLToPath } from "url";
547
- var PKG_NAME = "@primitive.ai/prim";
548
- var ROOT_WALK_LIMIT = 6;
549
- var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
550
- var resolvedRoot;
551
- function locateRoot() {
552
- if (resolvedRoot !== void 0) {
553
- return resolvedRoot;
554
- }
555
- let dir = dirname2(fileURLToPath(import.meta.url));
556
- for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
557
- const manifestPath = join(dir, "package.json");
558
- if (existsSync2(manifestPath)) {
559
- try {
560
- const manifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
561
- if (manifest.name === PKG_NAME && manifest.bin) {
562
- resolvedRoot = { dir, bin: manifest.bin };
563
- return resolvedRoot;
564
- }
565
- } catch {
566
- }
567
- }
568
- const parent = dirname2(dir);
569
- if (parent === dir) {
570
- break;
571
- }
572
- dir = parent;
573
- }
574
- resolvedRoot = null;
575
- return resolvedRoot;
576
- }
577
- function binFile(bin) {
578
- const root = locateRoot();
579
- const rel = root?.bin[bin];
580
- if (!root || !rel) {
581
- return null;
582
- }
583
- return isAbsolute(rel) ? rel : join(root.dir, rel);
584
- }
585
- function hookShimCommand(bin, args = "") {
586
- const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
587
- return `if command -v ${bin} >/dev/null 2>&1; then ${invoke(bin)}; elif [ -f "./node_modules/.bin/${bin}" ]; then ${invoke(`./node_modules/.bin/${bin}`)}; else ${invoke(`${NPX_FALLBACK} ${bin}`)}; fi`;
588
- }
589
- function detachedHookShimCommand(bin, args = "") {
590
- return `payload=$(cat); { trap '' HUP; export npm_config_fetch_retries=2 npm_config_fetch_retry_mintimeout=10000 npm_config_fetch_retry_maxtimeout=10000 npm_config_fetch_timeout=60000; printf '%s' "$payload" | { ${hookShimCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
591
- }
592
- function commandMatchesBin(command, bin) {
593
- if (!command) {
594
- return false;
595
- }
596
- const c = command.trim();
597
- if (c === bin || c.startsWith(`${bin} `)) {
598
- return true;
599
- }
600
- return c.includes(`command -v ${bin} `);
601
- }
602
-
603
- // src/commands/claude-install.ts
548
+ import { dirname as dirname2, join } from "path";
604
549
  var CAPTURE_BIN = "prim-hook";
605
550
  var GATE_BIN = "prim-pre-tool-use";
606
551
  var POST_TOOL_USE_BIN = "prim-post-tool-use";
@@ -618,11 +563,11 @@ var PRIM_BINS = [
618
563
  SESSION_END_BIN
619
564
  ];
620
565
  var JSON_INDENT = 2;
621
- var USER_SCOPE_PATH = join2(homedir(), ".claude", "settings.json");
566
+ var USER_SCOPE_PATH = join(homedir(), ".claude", "settings.json");
622
567
  function projectRoot() {
623
568
  return gitToplevel() ?? process.cwd();
624
569
  }
625
- var projectScopePath = () => join2(projectRoot(), ".claude", "settings.json");
570
+ var projectScopePath = () => join(projectRoot(), ".claude", "settings.json");
626
571
  var CAPTURE_EVENTS = [
627
572
  "SessionStart",
628
573
  "UserPromptSubmit",
@@ -632,8 +577,8 @@ var CAPTURE_EVENTS = [
632
577
  "SessionEnd",
633
578
  "SubagentStop"
634
579
  ];
635
- function makeRegistration(event, matcher, bin, args = "") {
636
- return { event, matcher, bin, command: hookShimCommand(bin, args) };
580
+ function makeRegistration(event, matcher, bin, args = "", opts = {}) {
581
+ return { event, matcher, bin, command: hookShimCommand(bin, args, opts) };
637
582
  }
638
583
  function makeDetachedRegistration(event, matcher, bin, args = "") {
639
584
  return { event, matcher, bin, command: detachedHookShimCommand(bin, args) };
@@ -644,7 +589,9 @@ var REGISTRATIONS = [
644
589
  ),
645
590
  makeRegistration("PreToolUse", "Edit|Write|MultiEdit", GATE_BIN),
646
591
  makeRegistration("PostToolUse", "Edit|Write|MultiEdit", POST_TOOL_USE_BIN),
647
- makeRegistration("SessionStart", "*", SESSION_START_BIN),
592
+ // Bare ladder (no branch-0): SessionStart must re-resolve @latest each session
593
+ // to refresh the bin cache the other hooks read. See lib/bin-cache.ts.
594
+ makeRegistration("SessionStart", "*", SESSION_START_BIN, "", { cacheRead: false }),
648
595
  makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
649
596
  ];
650
597
  var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim*)";
@@ -662,10 +609,10 @@ function settingsPathFor(scope) {
662
609
  return scope === "user" ? USER_SCOPE_PATH : projectScopePath();
663
610
  }
664
611
  function readSettings(path) {
665
- if (!existsSync3(path)) {
612
+ if (!existsSync2(path)) {
666
613
  return {};
667
614
  }
668
- const raw = readFileSync3(path, "utf-8");
615
+ const raw = readFileSync2(path, "utf-8");
669
616
  try {
670
617
  return JSON.parse(raw);
671
618
  } catch (err) {
@@ -806,8 +753,8 @@ function isGateInstalled(settings) {
806
753
  return (settings.hooks?.PreToolUse ?? []).some((e) => entryHasCommand(e, GATE_BIN));
807
754
  }
808
755
  function atomicWrite(path, content) {
809
- const dir = dirname3(path);
810
- if (!existsSync3(dir)) {
756
+ const dir = dirname2(path);
757
+ if (!existsSync2(dir)) {
811
758
  mkdirSync2(dir, { recursive: true });
812
759
  }
813
760
  const tmp = `${path}.tmp.${String(Date.now())}`;
@@ -955,7 +902,7 @@ ${line("project", result.project)}`);
955
902
 
956
903
  // src/commands/codex-install.ts
957
904
  import { homedir as homedir2 } from "os";
958
- import { join as join3 } from "path";
905
+ import { join as join2 } from "path";
959
906
  var CAPTURE_BIN2 = "prim-hook";
960
907
  var GATE_BIN2 = "prim-pre-tool-use";
961
908
  var POST_TOOL_USE_BIN2 = "prim-post-tool-use";
@@ -975,10 +922,12 @@ var CODEX_REGISTRATIONS = [
975
922
  ...CODEX_CAPTURE_EVENTS.map((event) => makeRegistration(event, "*", CAPTURE_BIN2, CODEX_ARGS)),
976
923
  makeRegistration("PreToolUse", "apply_patch", GATE_BIN2, CODEX_ARGS),
977
924
  makeRegistration("PostToolUse", "apply_patch", POST_TOOL_USE_BIN2, CODEX_ARGS),
978
- makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS)
925
+ // Bare ladder (no branch-0): SessionStart re-resolves @latest each session to
926
+ // refresh the bin cache the other hooks read. See lib/bin-cache.ts.
927
+ makeRegistration("SessionStart", "*", SESSION_START_BIN2, CODEX_ARGS, { cacheRead: false })
979
928
  ];
980
- var USER_SCOPE_PATH2 = join3(homedir2(), ".codex", "hooks.json");
981
- var projectScopePath2 = () => join3(projectRoot(), ".codex", "hooks.json");
929
+ var USER_SCOPE_PATH2 = join2(homedir2(), ".codex", "hooks.json");
930
+ var projectScopePath2 = () => join2(projectRoot(), ".codex", "hooks.json");
982
931
  function settingsPathFor2(scope) {
983
932
  return scope === "user" ? USER_SCOPE_PATH2 : projectScopePath2();
984
933
  }
@@ -1102,9 +1051,9 @@ ${line("project", result.project)}`);
1102
1051
 
1103
1052
  // src/commands/daemon.ts
1104
1053
  import { spawn } from "child_process";
1105
- import { closeSync as closeSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync4, unlinkSync } from "fs";
1054
+ import { closeSync as closeSync2, existsSync as existsSync3, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
1106
1055
  import { homedir as homedir3 } from "os";
1107
- import { join as join4 } from "path";
1056
+ import { join as join3 } from "path";
1108
1057
 
1109
1058
  // src/lib/presence.ts
1110
1059
  function formatTeammates(names, cap) {
@@ -1122,10 +1071,10 @@ function formatTeammates(names, cap) {
1122
1071
 
1123
1072
  // src/commands/daemon.ts
1124
1073
  var DAEMON_BIN = "prim-daemon-server";
1125
- var CONFIG_DIR = join4(homedir3(), ".config", "prim");
1126
- var PID_PATH = join4(CONFIG_DIR, "daemon.pid");
1127
- var SOCK_PATH = join4(CONFIG_DIR, "sock");
1128
- var LOG_PATH = join4(CONFIG_DIR, "daemon.log");
1074
+ var CONFIG_DIR = join3(homedir3(), ".config", "prim");
1075
+ var PID_PATH = join3(CONFIG_DIR, "daemon.pid");
1076
+ var SOCK_PATH = join3(CONFIG_DIR, "sock");
1077
+ var LOG_PATH = join3(CONFIG_DIR, "daemon.log");
1129
1078
  var CONFIG_DIR_MODE = 448;
1130
1079
  var LOG_FILE_MODE = 384;
1131
1080
  var STOP_TIMEOUT_MS = 5e3;
@@ -1138,10 +1087,10 @@ var EXIT_OK2 = 0;
1138
1087
  var EXIT_NOT_RUNNING = 2;
1139
1088
  var EXIT_BOOTING = 3;
1140
1089
  function readPidfile() {
1141
- if (!existsSync4(PID_PATH)) {
1090
+ if (!existsSync3(PID_PATH)) {
1142
1091
  return null;
1143
1092
  }
1144
- const raw = readFileSync4(PID_PATH, "utf-8").trim();
1093
+ const raw = readFileSync3(PID_PATH, "utf-8").trim();
1145
1094
  const pid = Number(raw);
1146
1095
  if (!Number.isInteger(pid) || pid <= 0) {
1147
1096
  return null;
@@ -1177,10 +1126,10 @@ function spawnDaemon(options) {
1177
1126
  return file ? spawn(process.execPath, [file], options) : spawn(DAEMON_BIN, [], options);
1178
1127
  }
1179
1128
  function openDaemonLog(configDir = CONFIG_DIR) {
1180
- if (!existsSync4(configDir)) {
1129
+ if (!existsSync3(configDir)) {
1181
1130
  mkdirSync3(configDir, { recursive: true, mode: CONFIG_DIR_MODE });
1182
1131
  }
1183
- return openSync2(join4(configDir, "daemon.log"), "a", LOG_FILE_MODE);
1132
+ return openSync2(join3(configDir, "daemon.log"), "a", LOG_FILE_MODE);
1184
1133
  }
1185
1134
  async function waitForReady() {
1186
1135
  const deadline = Date.now() + READY_TIMEOUT_MS;
@@ -2105,7 +2054,7 @@ function registerDecisionsCommands(program2) {
2105
2054
  "Functional area (auth, data, infra, ui, api, billing, mobile, docs, testing)"
2106
2055
  ).option("--decided <items>", "Comma-separated option(s) chosen").option("--alternatives <items>", "Comma-separated options considered but rejected").option("--confidence <level>", "high | medium | low (default high)").option("--reversibility <level>", "high | low (default high)").option(
2107
2056
  "--files <paths>",
2108
- "Comma-separated repo-relative paths this decision governs (gates edits to them)"
2057
+ "Comma-separated repo-relative paths this decision governs (the files Conflict Gates would check \u2014 not currently enabled)"
2109
2058
  ).action(async (opts) => {
2110
2059
  const request = {
2111
2060
  intent: opts.intent,
@@ -2171,7 +2120,7 @@ function registerDecisionsCommands(program2) {
2171
2120
  }
2172
2121
 
2173
2122
  // src/commands/doctor.ts
2174
- import { existsSync as existsSync5 } from "fs";
2123
+ import { existsSync as existsSync4 } from "fs";
2175
2124
  var DAEMON_PROBE_TIMEOUT_MS = 500;
2176
2125
  var CONNECTIVITY_TIMEOUT_MS = 3e3;
2177
2126
  var MS_PER_SECOND = 1e3;
@@ -2189,7 +2138,7 @@ function checkAuth() {
2189
2138
  return { name: "auth", status: "fail", detail: "no token \u2014 run `prim auth login`" };
2190
2139
  }
2191
2140
  const expiresAt = getTokenExpiresAt();
2192
- const hasRefresh = existsSync5(REFRESH_TOKEN_PATH);
2141
+ const hasRefresh = existsSync4(REFRESH_TOKEN_PATH);
2193
2142
  if (expiresAt !== void 0 && Date.now() >= expiresAt) {
2194
2143
  return hasRefresh ? { name: "auth", status: "warn", detail: "access token expired (refresh available)" } : {
2195
2144
  name: "auth",
@@ -2289,17 +2238,17 @@ function registerDoctorCommands(program2) {
2289
2238
  import {
2290
2239
  chmodSync,
2291
2240
  closeSync as closeSync3,
2292
- existsSync as existsSync6,
2241
+ existsSync as existsSync5,
2293
2242
  fsyncSync as fsyncSync2,
2294
2243
  mkdirSync as mkdirSync4,
2295
2244
  openSync as openSync3,
2296
- readFileSync as readFileSync5,
2245
+ readFileSync as readFileSync4,
2297
2246
  renameSync as renameSync2,
2298
2247
  rmSync as rmSync2,
2299
2248
  writeFileSync as writeFileSync3
2300
2249
  } from "fs";
2301
2250
  import { homedir as homedir4 } from "os";
2302
- import { dirname as dirname4, join as join5 } from "path";
2251
+ import { dirname as dirname3, join as join4 } from "path";
2303
2252
  import { Document, parseDocument, stringify } from "yaml";
2304
2253
  var CAPTURE_BIN3 = "prim-hook";
2305
2254
  var GATE_BIN3 = "prim-pre-tool-use";
@@ -2318,13 +2267,13 @@ var PRIM_BINS3 = [
2318
2267
  SESSION_END_BIN2
2319
2268
  ];
2320
2269
  function hermesHome() {
2321
- return process.env.HERMES_HOME ?? join5(homedir4(), ".hermes");
2270
+ return process.env.HERMES_HOME ?? join4(homedir4(), ".hermes");
2322
2271
  }
2323
2272
  function configPath() {
2324
- return join5(hermesHome(), "config.yaml");
2273
+ return join4(hermesHome(), "config.yaml");
2325
2274
  }
2326
2275
  function shimPath() {
2327
- return join5(hermesHome(), "agent-hooks", "prim-shim.sh");
2276
+ return join4(hermesHome(), "agent-hooks", "prim-shim.sh");
2328
2277
  }
2329
2278
  var SHIM_SCRIPT = `#!/bin/sh
2330
2279
  # prim Hermes hook shim \u2014 managed by \`prim hermes install\`. Hermes runs hooks
@@ -2413,10 +2362,10 @@ function isCaptureInstalled(hooks) {
2413
2362
  );
2414
2363
  }
2415
2364
  function readDoc(path) {
2416
- if (!existsSync6(path)) {
2365
+ if (!existsSync5(path)) {
2417
2366
  return new Document();
2418
2367
  }
2419
- const raw = readFileSync5(path, "utf-8");
2368
+ const raw = readFileSync4(path, "utf-8");
2420
2369
  return raw.trim().length === 0 ? new Document() : parseDocument(raw);
2421
2370
  }
2422
2371
  function readHooks(doc) {
@@ -2488,8 +2437,8 @@ function setAutoAccept(raw) {
2488
2437
  `;
2489
2438
  }
2490
2439
  function atomicWriteFile(path, content) {
2491
- const dir = dirname4(path);
2492
- if (!existsSync6(dir)) {
2440
+ const dir = dirname3(path);
2441
+ if (!existsSync5(dir)) {
2493
2442
  mkdirSync4(dir, { recursive: true });
2494
2443
  }
2495
2444
  const tmp = `${path}.tmp.${String(process.pid)}`;
@@ -2513,8 +2462,8 @@ function assertMergeValid(before, after) {
2513
2462
  }
2514
2463
  function writeShim() {
2515
2464
  const path = shimPath();
2516
- const dir = dirname4(path);
2517
- if (!existsSync6(dir)) {
2465
+ const dir = dirname3(path);
2466
+ if (!existsSync5(dir)) {
2518
2467
  mkdirSync4(dir, { recursive: true });
2519
2468
  }
2520
2469
  writeFileSync3(path, SHIM_SCRIPT, "utf-8");
@@ -2534,7 +2483,7 @@ function readHooksFromRaw(raw) {
2534
2483
  }
2535
2484
  function performInstall3(opts) {
2536
2485
  const path = configPath();
2537
- const raw = existsSync6(path) ? readFileSync5(path, "utf-8") : "";
2486
+ const raw = existsSync5(path) ? readFileSync4(path, "utf-8") : "";
2538
2487
  const existing = readHooksFromRaw(raw);
2539
2488
  const desired = applyInstall3(existing, opts.force);
2540
2489
  let next = raw;
@@ -2560,11 +2509,11 @@ function performInstall3(opts) {
2560
2509
  }
2561
2510
  function performUninstall3() {
2562
2511
  const path = configPath();
2563
- if (!existsSync6(path)) {
2512
+ if (!existsSync5(path)) {
2564
2513
  removeShim();
2565
2514
  return { path, gate: false, capture: false, autoAccept: false, changed: false };
2566
2515
  }
2567
- const raw = readFileSync5(path, "utf-8");
2516
+ const raw = readFileSync4(path, "utf-8");
2568
2517
  const existing = readHooksFromRaw(raw);
2569
2518
  const remaining = applyUninstall3(existing);
2570
2519
  const next = JSON.stringify(existing) === JSON.stringify(remaining) ? raw : spliceHooks(raw, remaining);
@@ -2584,7 +2533,7 @@ function performUninstall3() {
2584
2533
  }
2585
2534
  function performStatus3() {
2586
2535
  const path = configPath();
2587
- const hooks = existsSync6(path) ? readHooks(readDoc(path)) : {};
2536
+ const hooks = existsSync5(path) ? readHooks(readDoc(path)) : {};
2588
2537
  return { path, gate: isGateInstalled3(hooks), capture: isCaptureInstalled(hooks) };
2589
2538
  }
2590
2539
  var TRUST_NOTICE2 = "[prim] Hermes requires hook consent: it prompts once per (event, command) on a TTY and records approval in ~/.hermes/shell-hooks-allowlist.json. To pre-approve non-interactively, re-run with --auto-accept (sets hooks_auto_accept: true), export HERMES_ACCEPT_HOOKS=1, or start Hermes with `hermes --accept-hooks chat`. Until approved, the hooks will not fire.";
@@ -2629,9 +2578,9 @@ function registerHermesCommands(program2) {
2629
2578
 
2630
2579
  // src/commands/hooks.ts
2631
2580
  import { execFileSync } from "child_process";
2632
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
2581
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
2633
2582
  import { homedir as homedir5 } from "os";
2634
- import { dirname as dirname5, join as join6, resolve } from "path";
2583
+ import { dirname as dirname4, join as join5, resolve } from "path";
2635
2584
  import { Option } from "commander";
2636
2585
  var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
2637
2586
  var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
@@ -2680,15 +2629,15 @@ ${gatedShim(spec.binName)}
2680
2629
  ${end}`;
2681
2630
  }
2682
2631
  function mergePrimBlock(hookPath, block, binName) {
2683
- if (existsSync7(hookPath)) {
2684
- const existing = readFileSync6(hookPath, "utf-8");
2632
+ if (existsSync6(hookPath)) {
2633
+ const existing = readFileSync5(hookPath, "utf-8");
2685
2634
  if (containsPrimHook(existing, binName)) return false;
2686
2635
  const separator = existing.endsWith("\n") ? "\n" : "\n\n";
2687
2636
  writeFileSync4(hookPath, `${existing}${separator}${block}
2688
2637
  `, { mode: 493 });
2689
2638
  return true;
2690
2639
  }
2691
- mkdirSync5(dirname5(hookPath), { recursive: true });
2640
+ mkdirSync5(dirname4(hookPath), { recursive: true });
2692
2641
  writeFileSync4(hookPath, `#!/bin/sh
2693
2642
  # ${PRIM_CREATED_MARK}
2694
2643
 
@@ -2705,13 +2654,13 @@ function getGitRoot() {
2705
2654
  }
2706
2655
  function detectHusky(gitRoot) {
2707
2656
  const huskyDir = resolve(gitRoot, ".husky");
2708
- if (!existsSync7(huskyDir)) return false;
2709
- if (existsSync7(resolve(huskyDir, "_"))) return true;
2710
- if (existsSync7(resolve(huskyDir, "pre-commit"))) return true;
2657
+ if (!existsSync6(huskyDir)) return false;
2658
+ if (existsSync6(resolve(huskyDir, "_"))) return true;
2659
+ if (existsSync6(resolve(huskyDir, "pre-commit"))) return true;
2711
2660
  const pkgPath = resolve(gitRoot, "package.json");
2712
- if (existsSync7(pkgPath)) {
2661
+ if (existsSync6(pkgPath)) {
2713
2662
  try {
2714
- const pkg2 = JSON.parse(readFileSync6(pkgPath, "utf-8"));
2663
+ const pkg2 = JSON.parse(readFileSync5(pkgPath, "utf-8"));
2715
2664
  const scripts = pkg2.scripts ?? {};
2716
2665
  if (/husky/i.test(scripts.prepare ?? "") || /husky/i.test(scripts.postinstall ?? "")) {
2717
2666
  return true;
@@ -2738,7 +2687,7 @@ async function askConfirmation(question) {
2738
2687
  }
2739
2688
  function installToHusky(gitRoot, spec = PRE_COMMIT) {
2740
2689
  const hookPath = resolve(gitRoot, ".husky", spec.hookName);
2741
- const existed = existsSync7(hookPath);
2690
+ const existed = existsSync6(hookPath);
2742
2691
  const wrote = mergePrimBlock(hookPath, huskyBlock(spec), spec.binName);
2743
2692
  if (!wrote) {
2744
2693
  console.log(`Prim ${spec.hookName} hook is already installed in .husky/${spec.hookName}.`);
@@ -2751,11 +2700,11 @@ function installToHusky(gitRoot, spec = PRE_COMMIT) {
2751
2700
  function installToDotGit(gitRoot, spec = PRE_COMMIT) {
2752
2701
  const hooksDir = resolve(gitRoot, ".git", "hooks");
2753
2702
  const hookPath = resolve(hooksDir, spec.hookName);
2754
- if (!existsSync7(hooksDir)) {
2703
+ if (!existsSync6(hooksDir)) {
2755
2704
  mkdirSync5(hooksDir, { recursive: true });
2756
2705
  }
2757
- if (existsSync7(hookPath)) {
2758
- const existing = readFileSync6(hookPath, "utf-8");
2706
+ if (existsSync6(hookPath)) {
2707
+ const existing = readFileSync5(hookPath, "utf-8");
2759
2708
  if (containsPrimHook(existing, spec.binName)) {
2760
2709
  console.log(`Prim ${spec.hookName} hook is already installed at ${hookPath}.`);
2761
2710
  return;
@@ -2767,9 +2716,9 @@ function installToDotGit(gitRoot, spec = PRE_COMMIT) {
2767
2716
  writeFileSync4(hookPath, dotGitScript(spec), { mode: 493 });
2768
2717
  console.log(`Installed ${spec.hookName} hook at ${hookPath}`);
2769
2718
  }
2770
- var PRIM_GIT_HOOKS_DIR = join6(homedir5(), ".config", "prim", "git-hooks");
2719
+ var PRIM_GIT_HOOKS_DIR = join5(homedir5(), ".config", "prim", "git-hooks");
2771
2720
  function expandTilde(p) {
2772
- return p.startsWith("~/") ? join6(homedir5(), p.slice(2)) : p;
2721
+ return p.startsWith("~/") ? join5(homedir5(), p.slice(2)) : p;
2773
2722
  }
2774
2723
  function isOurHooksDir(value) {
2775
2724
  return value !== "" && expandTilde(value) === PRIM_GIT_HOOKS_DIR;
@@ -2834,7 +2783,7 @@ function ownedHookNames() {
2834
2783
  return [...HOOKS.map((s) => s.hookName), ...PASSTHROUGH_HOOKS];
2835
2784
  }
2836
2785
  function writeOwnHooks() {
2837
- if (!existsSync7(PRIM_GIT_HOOKS_DIR)) {
2786
+ if (!existsSync6(PRIM_GIT_HOOKS_DIR)) {
2838
2787
  mkdirSync5(PRIM_GIT_HOOKS_DIR, { recursive: true });
2839
2788
  }
2840
2789
  for (const spec of HOOKS) {
@@ -2850,8 +2799,8 @@ function appendPrimBlock(hookPath, spec) {
2850
2799
  mergePrimBlock(hookPath, gatedBlock(spec), spec.binName);
2851
2800
  }
2852
2801
  function stripPrimBlock(hookPath, spec) {
2853
- if (!existsSync7(hookPath)) return;
2854
- const existing = readFileSync6(hookPath, "utf-8");
2802
+ if (!existsSync6(hookPath)) return;
2803
+ const existing = readFileSync5(hookPath, "utf-8");
2855
2804
  const primCreated = existing.includes(PRIM_CREATED_MARK);
2856
2805
  const { start, end } = blockMarkers(spec);
2857
2806
  const s = existing.indexOf(start);
@@ -2906,7 +2855,7 @@ function uninstallGlobalHooks() {
2906
2855
  if (isOurHooksDir(global)) {
2907
2856
  for (const name of ownedHookNames()) {
2908
2857
  const p = resolve(PRIM_GIT_HOOKS_DIR, name);
2909
- if (existsSync7(p)) unlinkSync2(p);
2858
+ if (existsSync6(p)) unlinkSync2(p);
2910
2859
  }
2911
2860
  execFileSync("git", ["config", "--global", "--unset", "core.hooksPath"]);
2912
2861
  console.log("Removed prim global git hooks and unset core.hooksPath.");
@@ -2996,11 +2945,11 @@ function registerHooksCommands(program2) {
2996
2945
  const gitRoot = getGitRoot();
2997
2946
  for (const spec of HOOKS) {
2998
2947
  const hookPath = resolve(gitRoot, ".git", "hooks", spec.hookName);
2999
- if (!existsSync7(hookPath)) {
2948
+ if (!existsSync6(hookPath)) {
3000
2949
  console.log(`No ${spec.hookName} hook found.`);
3001
2950
  continue;
3002
2951
  }
3003
- if (containsPrimHook(readFileSync6(hookPath, "utf-8"), spec.binName)) {
2952
+ if (containsPrimHook(readFileSync5(hookPath, "utf-8"), spec.binName)) {
3004
2953
  unlinkSync2(hookPath);
3005
2954
  console.log(`Removed ${spec.hookName} hook at ${hookPath}`);
3006
2955
  } else {
@@ -3011,8 +2960,8 @@ function registerHooksCommands(program2) {
3011
2960
  }
3012
2961
 
3013
2962
  // src/commands/moves.ts
3014
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
3015
- import { join as join7 } from "path";
2963
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
2964
+ import { join as join6 } from "path";
3016
2965
 
3017
2966
  // src/flusher.ts
3018
2967
  import { renameSync as renameSync3, unlinkSync as unlinkSync3 } from "fs";
@@ -3171,19 +3120,19 @@ function registerMovesCommands(program2) {
3171
3120
  }
3172
3121
  });
3173
3122
  moves.command("bind").description("Pin the current directory to an org via .prim/workspace.json").requiredOption("--orgId <orgId>", "Convex organization id").action((opts) => {
3174
- const dir = join7(process.cwd(), ".prim");
3175
- if (!existsSync8(dir)) {
3123
+ const dir = join6(process.cwd(), ".prim");
3124
+ if (!existsSync7(dir)) {
3176
3125
  mkdirSync6(dir, { recursive: true, mode: DIR_MODE });
3177
3126
  }
3178
- const file = join7(process.cwd(), WORKSPACE_FILE);
3127
+ const file = join6(process.cwd(), WORKSPACE_FILE);
3179
3128
  writeFileSync5(file, JSON.stringify({ orgId: opts.orgId, boundAt: Date.now() }, null, 2), {
3180
3129
  mode: FILE_MODE2
3181
3130
  });
3182
3131
  console.log(`[prim] bound ${process.cwd()} to org ${opts.orgId}`);
3183
3132
  });
3184
3133
  moves.command("drop").description("Remove the .prim/workspace.json binding from the cwd").action(() => {
3185
- const file = join7(process.cwd(), WORKSPACE_FILE);
3186
- if (!existsSync8(file)) {
3134
+ const file = join6(process.cwd(), WORKSPACE_FILE);
3135
+ if (!existsSync7(file)) {
3187
3136
  console.log("[prim] no workspace binding in cwd");
3188
3137
  return;
3189
3138
  }
@@ -3265,7 +3214,7 @@ async function performReconcile(idOrShortId, opts = {}) {
3265
3214
  }
3266
3215
  function registerReconcileCommands(program2) {
3267
3216
  program2.command("reconcile <idOrShortId>").description(
3268
- "Issue a single-use bypass for a flagged decision (used by the cooperative reconcile loop)"
3217
+ "Issue a single-use bypass for a decision flagged by Conflict Gates Enforcement (not currently enabled)"
3269
3218
  ).option(
3270
3219
  "--flag <conflictFlagId>",
3271
3220
  "Specific flag id to bind the bypass to (default: the decision's latest unack'd flag)"
@@ -3276,23 +3225,23 @@ function registerReconcileCommands(program2) {
3276
3225
 
3277
3226
  // src/commands/session.ts
3278
3227
  import {
3279
- existsSync as existsSync9,
3228
+ existsSync as existsSync8,
3280
3229
  mkdirSync as mkdirSync7,
3281
- readFileSync as readFileSync7,
3230
+ readFileSync as readFileSync6,
3282
3231
  readdirSync,
3283
3232
  unlinkSync as unlinkSync5,
3284
3233
  writeFileSync as writeFileSync6
3285
3234
  } from "fs";
3286
- import { join as join8 } from "path";
3235
+ import { join as join7 } from "path";
3287
3236
  var DIR_MODE2 = 448;
3288
3237
  var FILE_MODE3 = 384;
3289
3238
  function ensureDir() {
3290
- if (!existsSync9(SESSIONS_DIR)) {
3239
+ if (!existsSync8(SESSIONS_DIR)) {
3291
3240
  mkdirSync7(SESSIONS_DIR, { recursive: true, mode: DIR_MODE2 });
3292
3241
  }
3293
3242
  }
3294
3243
  function markerPath(sessionId) {
3295
- return join8(SESSIONS_DIR, `${sessionId}.json`);
3244
+ return join7(SESSIONS_DIR, `${sessionId}.json`);
3296
3245
  }
3297
3246
  function registerSessionCommands(program2) {
3298
3247
  const session = program2.command("session").description("Decision Event Pipeline \u2014 session binding markers");
@@ -3308,7 +3257,7 @@ function registerSessionCommands(program2) {
3308
3257
  console.log(`[prim] session ${sessionId} bound to org ${opts.orgId}`);
3309
3258
  });
3310
3259
  session.command("list").description("List active session markers").action(() => {
3311
- if (!existsSync9(SESSIONS_DIR)) {
3260
+ if (!existsSync8(SESSIONS_DIR)) {
3312
3261
  console.log("[prim] no session markers");
3313
3262
  return;
3314
3263
  }
@@ -3320,7 +3269,7 @@ function registerSessionCommands(program2) {
3320
3269
  for (const f of files) {
3321
3270
  const sessionId = f.replace(/\.json$/, "");
3322
3271
  try {
3323
- const m = JSON.parse(readFileSync7(join8(SESSIONS_DIR, f), "utf-8"));
3272
+ const m = JSON.parse(readFileSync6(join7(SESSIONS_DIR, f), "utf-8"));
3324
3273
  console.log(`${sessionId} org=${m.orgId}`);
3325
3274
  } catch {
3326
3275
  }
@@ -3328,7 +3277,7 @@ function registerSessionCommands(program2) {
3328
3277
  });
3329
3278
  session.command("drop <sessionId>").description("Remove a session marker").action((sessionId) => {
3330
3279
  const p = markerPath(sessionId);
3331
- if (!existsSync9(p)) {
3280
+ if (!existsSync8(p)) {
3332
3281
  console.log(`[prim] no marker for session ${sessionId}`);
3333
3282
  return;
3334
3283
  }
@@ -3339,8 +3288,8 @@ function registerSessionCommands(program2) {
3339
3288
 
3340
3289
  // src/commands/setup.ts
3341
3290
  import { spawnSync } from "child_process";
3342
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
3343
- import { join as join9 } from "path";
3291
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
3292
+ import { join as join8 } from "path";
3344
3293
  var EXIT_INCOMPLETE = 1;
3345
3294
  var EXIT_USAGE3 = 2;
3346
3295
  var SESSION_LABELS = {
@@ -3403,8 +3352,8 @@ function detectProjectConflicts(agent, run) {
3403
3352
  }
3404
3353
  try {
3405
3354
  const root = gitToplevel();
3406
- const preCommit = root && join9(root, ".git", "hooks", "pre-commit");
3407
- if (preCommit && existsSync10(preCommit) && readFileSync8(preCommit, "utf-8").includes("prim-pre-commit")) {
3355
+ const preCommit = root && join8(root, ".git", "hooks", "pre-commit");
3356
+ if (preCommit && existsSync9(preCommit) && readFileSync7(preCommit, "utf-8").includes("prim-pre-commit")) {
3408
3357
  conflicts.push(CONFLICT_HOOKS);
3409
3358
  }
3410
3359
  } catch {
@@ -3522,39 +3471,39 @@ function registerSetupCommand(program2) {
3522
3471
  // src/commands/skill.ts
3523
3472
  import {
3524
3473
  closeSync as closeSync4,
3525
- existsSync as existsSync12,
3474
+ existsSync as existsSync11,
3526
3475
  fsyncSync as fsyncSync3,
3527
3476
  openSync as openSync4,
3528
- readFileSync as readFileSync10,
3477
+ readFileSync as readFileSync9,
3529
3478
  renameSync as renameSync4,
3530
3479
  writeFileSync as writeFileSync7
3531
3480
  } from "fs";
3532
3481
  import { homedir as homedir7 } from "os";
3533
- import { dirname as dirname7, join as join11, resolve as resolve3 } from "path";
3534
- import { fileURLToPath as fileURLToPath3 } from "url";
3482
+ import { dirname as dirname6, join as join10, resolve as resolve3 } from "path";
3483
+ import { fileURLToPath as fileURLToPath2 } from "url";
3535
3484
  import { createPatch } from "diff";
3536
3485
 
3537
3486
  // src/commands/claude-plugin.ts
3538
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
3487
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync2, rmSync as rmSync3, rmdirSync } from "fs";
3539
3488
  import { homedir as homedir6 } from "os";
3540
- import { dirname as dirname6, join as join10, resolve as resolve2 } from "path";
3541
- import { fileURLToPath as fileURLToPath2 } from "url";
3542
- var __dirname = dirname6(fileURLToPath2(import.meta.url));
3489
+ import { dirname as dirname5, join as join9, resolve as resolve2 } from "path";
3490
+ import { fileURLToPath } from "url";
3491
+ var __dirname = dirname5(fileURLToPath(import.meta.url));
3543
3492
  var PLUGIN_DESCRIPTION = "Primitive decision-graph guidance for the prim CLI \u2014 a model-invoked skill installed by prim skill install.";
3544
3493
  function resolvePluginDir(cwd, scope) {
3545
3494
  if (scope && scope !== "user" && scope !== "project") {
3546
3495
  console.error(`Unknown --scope "${scope}" (expected user or project)`);
3547
3496
  return null;
3548
3497
  }
3549
- const base = scope === "user" ? join10(homedir6(), ".claude") : join10(gitToplevel(cwd) ?? cwd, ".claude");
3550
- return join10(base, "skills", "prim");
3498
+ const base = scope === "user" ? join9(homedir6(), ".claude") : join9(gitToplevel(cwd) ?? cwd, ".claude");
3499
+ return join9(base, "skills", "prim");
3551
3500
  }
3552
3501
  function packageVersion() {
3553
3502
  let dir = __dirname;
3554
- while (dir !== dirname6(dir)) {
3503
+ while (dir !== dirname5(dir)) {
3555
3504
  const p = resolve2(dir, "package.json");
3556
- if (existsSync11(p)) return JSON.parse(readFileSync9(p, "utf-8")).version;
3557
- dir = dirname6(dir);
3505
+ if (existsSync10(p)) return JSON.parse(readFileSync8(p, "utf-8")).version;
3506
+ dir = dirname5(dir);
3558
3507
  }
3559
3508
  return "0.0.0";
3560
3509
  }
@@ -3564,12 +3513,12 @@ function pluginManifest() {
3564
3513
  `;
3565
3514
  }
3566
3515
  function removeDirIfEmpty(dir) {
3567
- if (existsSync11(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
3516
+ if (existsSync10(dir) && readdirSync2(dir).length === 0) rmdirSync(dir);
3568
3517
  }
3569
3518
  function pluginPaths(dir) {
3570
3519
  return {
3571
- manifestPath: join10(dir, ".claude-plugin", "plugin.json"),
3572
- skillPath: join10(dir, "SKILL.md")
3520
+ manifestPath: join9(dir, ".claude-plugin", "plugin.json"),
3521
+ skillPath: join9(dir, "SKILL.md")
3573
3522
  };
3574
3523
  }
3575
3524
  function installClaudePlugin(cwd, opts) {
@@ -3578,8 +3527,8 @@ function installClaudePlugin(cwd, opts) {
3578
3527
  const { manifestPath, skillPath } = pluginPaths(dir);
3579
3528
  const manifest = pluginManifest();
3580
3529
  const skill = loadSkill();
3581
- const manifestCurrent = existsSync11(manifestPath) ? readFileSync9(manifestPath, "utf-8") : null;
3582
- const skillCurrent = existsSync11(skillPath) ? readFileSync9(skillPath, "utf-8") : null;
3530
+ const manifestCurrent = existsSync10(manifestPath) ? readFileSync8(manifestPath, "utf-8") : null;
3531
+ const skillCurrent = existsSync10(skillPath) ? readFileSync8(skillPath, "utf-8") : null;
3583
3532
  if (manifestCurrent === manifest && skillCurrent === skill) {
3584
3533
  console.log("No changes \u2014 prim skill plugin already up to date.");
3585
3534
  return 0;
@@ -3588,7 +3537,7 @@ function installClaudePlugin(cwd, opts) {
3588
3537
  console.log(`Would write plugin to ${dir} (.claude-plugin/plugin.json + SKILL.md)`);
3589
3538
  return 0;
3590
3539
  }
3591
- mkdirSync8(join10(dir, ".claude-plugin"), { recursive: true });
3540
+ mkdirSync8(join9(dir, ".claude-plugin"), { recursive: true });
3592
3541
  atomicWrite2(manifestPath, manifest);
3593
3542
  atomicWrite2(skillPath, skill);
3594
3543
  console.log(`Installed prim skill plugin at ${dir}`);
@@ -3602,13 +3551,13 @@ function uninstallClaudePlugin(cwd, opts) {
3602
3551
  const dir = resolvePluginDir(cwd, opts.scope);
3603
3552
  if (dir === null) return 1;
3604
3553
  const { manifestPath, skillPath } = pluginPaths(dir);
3605
- if (!existsSync11(manifestPath) && !existsSync11(skillPath)) {
3554
+ if (!existsSync10(manifestPath) && !existsSync10(skillPath)) {
3606
3555
  console.log(`prim skill plugin not present at ${dir}`);
3607
3556
  return 0;
3608
3557
  }
3609
3558
  rmSync3(manifestPath, { force: true });
3610
3559
  rmSync3(skillPath, { force: true });
3611
- removeDirIfEmpty(join10(dir, ".claude-plugin"));
3560
+ removeDirIfEmpty(join9(dir, ".claude-plugin"));
3612
3561
  removeDirIfEmpty(dir);
3613
3562
  console.log(`Removed prim skill plugin from ${dir}`);
3614
3563
  return 0;
@@ -3617,7 +3566,7 @@ function statusClaudePlugin(cwd, opts) {
3617
3566
  const dir = resolvePluginDir(cwd, opts.scope);
3618
3567
  if (dir === null) return 1;
3619
3568
  const { skillPath } = pluginPaths(dir);
3620
- const installed = existsSync11(skillPath);
3569
+ const installed = existsSync10(skillPath);
3621
3570
  if (opts.json) {
3622
3571
  printJson({ installed, target: dir });
3623
3572
  return installed ? 0 : 1;
@@ -3631,7 +3580,7 @@ function statusClaudePlugin(cwd, opts) {
3631
3580
  }
3632
3581
 
3633
3582
  // src/commands/skill.ts
3634
- var __dirname2 = dirname7(fileURLToPath3(import.meta.url));
3583
+ var __dirname2 = dirname6(fileURLToPath2(import.meta.url));
3635
3584
  var SKILL_BEGIN = "<!-- BEGIN PRIM SKILL v1 -->";
3636
3585
  var SKILL_END = "<!-- END PRIM SKILL v1 -->";
3637
3586
  var TARGET_CANDIDATES = [
@@ -3649,24 +3598,24 @@ var AGENT_TARGET = {
3649
3598
  hermes: ".hermes.md"
3650
3599
  };
3651
3600
  function userTargetFor(agent) {
3652
- if (agent === "claude") return join11(homedir7(), ".claude", "CLAUDE.md");
3653
- if (agent === "codex") return join11(homedir7(), ".codex", "AGENTS.md");
3601
+ if (agent === "claude") return join10(homedir7(), ".claude", "CLAUDE.md");
3602
+ if (agent === "codex") return join10(homedir7(), ".codex", "AGENTS.md");
3654
3603
  if (agent === "hermes") {
3655
- return join11(process.env.HERMES_HOME ?? join11(homedir7(), ".hermes"), ".hermes.md");
3604
+ return join10(process.env.HERMES_HOME ?? join10(homedir7(), ".hermes"), ".hermes.md");
3656
3605
  }
3657
3606
  return null;
3658
3607
  }
3659
3608
  function loadSkill() {
3660
3609
  let dir = __dirname2;
3661
- while (dir !== dirname7(dir)) {
3610
+ while (dir !== dirname6(dir)) {
3662
3611
  const p = resolve3(dir, "SKILL.md");
3663
- if (existsSync12(p)) return readFileSync10(p, "utf-8");
3664
- dir = dirname7(dir);
3612
+ if (existsSync11(p)) return readFileSync9(p, "utf-8");
3613
+ dir = dirname6(dir);
3665
3614
  }
3666
3615
  throw new Error("SKILL.md not found in package");
3667
3616
  }
3668
3617
  function detectTargets(cwd) {
3669
- return TARGET_CANDIDATES.filter((p) => existsSync12(resolve3(cwd, p)));
3618
+ return TARGET_CANDIDATES.filter((p) => existsSync11(resolve3(cwd, p)));
3670
3619
  }
3671
3620
  function detectNewline(content) {
3672
3621
  return content.includes("\r\n") ? "\r\n" : "\n";
@@ -3736,7 +3685,7 @@ function runInstall(cwd, opts) {
3736
3685
  if (opts.agent === "claude" && !opts.target) return installClaudePlugin(cwd, opts);
3737
3686
  const target = resolveTarget(cwd, opts);
3738
3687
  if (target === null) return 1;
3739
- const existing = existsSync12(target) ? readFileSync10(target, "utf-8") : "";
3688
+ const existing = existsSync11(target) ? readFileSync9(target, "utf-8") : "";
3740
3689
  const eol = existing ? detectNewline(existing) : "\n";
3741
3690
  const block = composeBlock(loadSkill(), eol);
3742
3691
  const next = applyBlock(existing, block, eol);
@@ -3756,11 +3705,11 @@ function runUninstall(cwd, opts) {
3756
3705
  if (opts.agent === "claude" && !opts.target) return uninstallClaudePlugin(cwd, opts);
3757
3706
  const target = resolveTarget(cwd, opts);
3758
3707
  if (target === null) return 1;
3759
- if (!existsSync12(target)) {
3708
+ if (!existsSync11(target)) {
3760
3709
  console.log(`Skill block not present at ${target}`);
3761
3710
  return 0;
3762
3711
  }
3763
- const existing = readFileSync10(target, "utf-8");
3712
+ const existing = readFileSync9(target, "utf-8");
3764
3713
  const next = removeBlock(existing);
3765
3714
  if (next === null) {
3766
3715
  console.log(`Skill block not present at ${target}`);
@@ -3774,10 +3723,10 @@ function runStatus(cwd, opts) {
3774
3723
  if (opts.agent === "claude" && !opts.target) return statusClaudePlugin(cwd, opts);
3775
3724
  const target = resolveTarget(cwd, opts);
3776
3725
  if (target === null) return 1;
3777
- const fileExists = existsSync12(target);
3726
+ const fileExists = existsSync11(target);
3778
3727
  let installed = false;
3779
3728
  if (fileExists) {
3780
- const content = readFileSync10(target, "utf-8");
3729
+ const content = readFileSync9(target, "utf-8");
3781
3730
  installed = content.includes(SKILL_BEGIN) && content.includes(SKILL_END);
3782
3731
  }
3783
3732
  if (opts.json) {
@@ -3814,18 +3763,18 @@ function registerSkillCommands(program2) {
3814
3763
  }
3815
3764
 
3816
3765
  // src/commands/statusline.ts
3817
- import { readFileSync as readFileSync11 } from "fs";
3818
- import { dirname as dirname8, resolve as resolve4 } from "path";
3819
- import { fileURLToPath as fileURLToPath4 } from "url";
3766
+ import { readFileSync as readFileSync10 } from "fs";
3767
+ import { dirname as dirname7, resolve as resolve4 } from "path";
3768
+ import { fileURLToPath as fileURLToPath3 } from "url";
3820
3769
  var STATUSLINE_TIMEOUT_MS = 200;
3821
3770
  var STATUSLINE_NAME_CAP = 3;
3822
3771
  function readPackageVersion() {
3823
3772
  try {
3824
- const here = dirname8(fileURLToPath4(import.meta.url));
3773
+ const here = dirname7(fileURLToPath3(import.meta.url));
3825
3774
  const candidates = [resolve4(here, "../../package.json"), resolve4(here, "../package.json")];
3826
3775
  for (const path of candidates) {
3827
3776
  try {
3828
- const pkg2 = JSON.parse(readFileSync11(path, "utf-8"));
3777
+ const pkg2 = JSON.parse(readFileSync10(path, "utf-8"));
3829
3778
  if (pkg2.version) {
3830
3779
  return pkg2.version;
3831
3780
  }
@@ -3874,6 +3823,7 @@ async function renderStatusline() {
3874
3823
  function registerStatuslineCommands(program2) {
3875
3824
  program2.command("statusline").description("Render the Claude Code statusLine for the prim companion daemon").action(async () => {
3876
3825
  try {
3826
+ warmBinCache();
3877
3827
  const line = await renderStatusline();
3878
3828
  process.stdout.write(line);
3879
3829
  } catch {
@@ -3925,14 +3875,13 @@ function formatWelcome(state) {
3925
3875
  bold(color("Welcome to Primitive", "green")),
3926
3876
  "",
3927
3877
  "Primitive captures the decisions your team makes while coding into a",
3928
- "shared graph \u2014 and flags edits that conflict with earlier ones before",
3929
- "they land.",
3878
+ "shared graph \u2014 automatically, as you work.",
3930
3879
  "",
3931
3880
  bold("How it works"),
3932
3881
  bullet("Capture is automatic \u2014 keep coding; your decisions are recorded for you."),
3933
- bullet("The conflict gate has your back: when an edit conflicts with a"),
3934
- " load-bearing decision, prim surfaces it. Run `prim reconcile dec_<id>` to clear",
3935
- " that decision and retry.",
3882
+ bullet("Conflict Gates (with Enforcement) flag or block edits that conflict"),
3883
+ " with a load-bearing decision \u2014 not currently enabled. Contact",
3884
+ " support@getprimitive.ai to turn them on for your team.",
3936
3885
  bullet('Occasional yes/no prompts confirm the "why" behind a decision \u2014'),
3937
3886
  " answering keeps the graph trustworthy.",
3938
3887
  ""
@@ -3994,8 +3943,8 @@ function registerWelcomeCommand(program2, deps = { getClient }) {
3994
3943
  }
3995
3944
 
3996
3945
  // src/index.ts
3997
- var __dirname3 = dirname9(fileURLToPath5(import.meta.url));
3998
- var pkg = JSON.parse(readFileSync12(resolve5(__dirname3, "../package.json"), "utf-8"));
3946
+ var __dirname3 = dirname8(fileURLToPath4(import.meta.url));
3947
+ var pkg = JSON.parse(readFileSync11(resolve5(__dirname3, "../package.json"), "utf-8"));
3999
3948
  updateNotifier({ pkg }).notify();
4000
3949
  var program = new Command();
4001
3950
  program.name("prim").description("CLI for Primitive's decision graph").version(pkg.version).option("-y, --yes", "auto-confirm prompts").option(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.35",
3
+ "version": "0.1.0-alpha.37",
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",