@primitive.ai/prim 0.1.0-alpha.48 → 0.1.0-alpha.49

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
@@ -11,8 +11,8 @@ presence — from the command line and via session + git hooks.
11
11
  > **Conflict Gates** check each edit against the decision graph and surface any
12
12
  > load-bearing decision it conflicts with; their **Enforcement** tier blocks or pauses
13
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**.
14
+ > enabled** — automatic decision capture in active repos runs regardless. To enable them
15
+ > for your team, contact **support@getprimitive.ai**.
16
16
 
17
17
  ## Installation
18
18
 
@@ -28,6 +28,12 @@ Or run directly without installing:
28
28
  npx @primitive.ai/prim
29
29
  ```
30
30
 
31
+ In commands such as `npx --yes @primitive.ai/prim@latest ...`, the first `--yes`
32
+ belongs to **npm**: it skips npm's package-install confirmation. It does not approve a
33
+ Primitive action. Prim's own global `--yes` comes after the package name, for example
34
+ `npx --yes @primitive.ai/prim@latest --yes decisions create ...`, and applies only to
35
+ that Prim invocation.
36
+
31
37
  ## Quick Start
32
38
 
33
39
  One command does the whole install — auth, session hooks, daemon, git hooks,
@@ -118,6 +124,24 @@ prim codex install # Install OpenAI Codex hooks (project scope)
118
124
  prim hermes install # Install Hermes Agent hooks (global ~/.hermes/config.yaml)
119
125
  ```
120
126
 
127
+ Passive capture is repo-scoped even when these integrations are installed at user
128
+ scope. `prim enable` marks the current Git repo active; `prim disable` makes it
129
+ inactive. A project-scoped install activates its repo automatically. In an inactive
130
+ repo, session content is not passively captured. Here “inactive” means Prim's normal
131
+ effective capture check is false, including an explicit local `prim.active=false`.
132
+
133
+ An agent may still deliberately record one Decision from an inactive repo with
134
+ `prim decisions create`, but each invocation needs its own user approval. An explicit
135
+ request such as “record this Decision” supplies that approval for that one create; for
136
+ a proactive suggestion, the agent must show the proposed Decision and ask first. The
137
+ agent then passes Prim's global `--yes` for that invocation. This one-time approval does
138
+ not activate passive capture, and neither npm's `npx --yes`, setup approval, nor an
139
+ earlier create counts as approval for another Decision.
140
+
141
+ When a person runs `prim decisions create` interactively in an inactive repo, Prim
142
+ asks for that one-time approval itself. In non-interactive use it creates nothing
143
+ unless Prim's global `--yes` is present.
144
+
121
145
  #### Claude decision feedback
122
146
 
123
147
  Claude Code receives eventual, human-visible feedback when automatic capture
@@ -199,6 +223,15 @@ prim decisions unlink <child> --on <parent> # Remove that dependency
199
223
  `<id>` accepts a full decision ID or its short ID. STDOUT is machine-readable
200
224
  JSON; human-readable status goes to STDERR.
201
225
 
226
+ When passive capture is inactive in the current repo, an approved one-time create is:
227
+
228
+ ```bash
229
+ npx --yes @primitive.ai/prim@latest --yes decisions create --intent "…"
230
+ ```
231
+
232
+ Here npm's first `--yes` only permits package resolution; Prim's second `--yes`
233
+ confirms this create. It does not enable the repo or authorize a later create.
234
+
202
235
  `link` / `unlink` curate the dependency edges the automatic linker would otherwise
203
236
  own — `<child>` depends on `<parent>`. Both are idempotent and refuse any link that
204
237
  would create a cycle (exit 2); an unresolved id exits 4.
@@ -245,8 +278,8 @@ prim welcome # Brief orientation to the decision graph (shown after se
245
278
  ### Session & journal
246
279
 
247
280
  Lower-level plumbing for the capture pipeline — org binding and the local move
248
- journal. Capture works automatically once the session hooks are installed; these
249
- are for inspecting and steering it (e.g. multi-org machines).
281
+ journal. Capture works automatically once the session hooks are installed **and the
282
+ repo is active**; these are for inspecting and steering it (e.g. multi-org machines).
250
283
 
251
284
  ```bash
252
285
  prim session start <id> # Pin a Claude Code session to an org (list / drop <id>)
package/SKILL.md CHANGED
@@ -9,9 +9,9 @@ description: Use the prim CLI for Primitive's decision graph — passive capture
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. **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.
12
+ In an active repo, prim passively captures the **decisions** your team makes -- 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 in active repos runs regardless. To enable them for your team, contact support@getprimitive.ai.
13
13
 
14
- Low-level capture 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). Deliberately record higher-order forks in the road through `prim decisions create` as described below. Your job is also 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.)
14
+ Low-level capture runs automatically in active repos 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). An inactive repo is not passively captured. Deliberately record higher-order forks in the road through `prim decisions create` as described below. Your job is also 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
 
@@ -30,10 +30,11 @@ The CLI auto-refreshes a still-valid session from the stored refresh token (proa
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
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
+ 4. In `npx --yes @primitive.ai/prim ...`, `npx --yes` is npm's flag and only skips npm's package-install confirmation. It is not user consent for a Primitive action. Prim's global flag appears after the package name: `npx --yes @primitive.ai/prim --yes ...`.
33
34
 
34
35
  ## Conflict Gates & Enforcement (not currently enabled)
35
36
 
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
+ **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 in active repos (above) runs regardless. To enable Conflict Gates and Enforcement for your team, contact support@getprimitive.ai.
37
38
 
38
39
  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:
39
40
 
@@ -77,7 +78,7 @@ Confirmations are author-targeted and rare by design; answering keeps the graph'
77
78
 
78
79
  ## Preserve durable user decisions
79
80
 
80
- Capture is automatic for low-level choices made while coding. Use the deliberate CLI path for higher-order decisions that emerge in conversation: goals, priorities, principles, invariants, constraints, defaults, commitments, durable tradeoffs, and exceptions.
81
+ Capture is automatic for low-level choices made while coding in an active repo. Use the deliberate CLI path for higher-order decisions that emerge in conversation: goals, priorities, principles, invariants, constraints, defaults, commitments, durable tradeoffs, and exceptions.
81
82
 
82
83
  A decision worth deliberately recording is a genuine **fork in the road**: the user or agent encountered multiple plausible paths, selected one, and that selection should inform future work. Record the chosen behavior, direction, constraint, or tradeoff—not routine implementation needed to finish the task or follow an existing convention. A teammate working elsewhere should benefit from knowing it.
83
84
 
@@ -87,7 +88,7 @@ Before deliberately recording a decision, actively gather the real context behin
87
88
 
88
89
  Record only rationale supported by those sources. Do not mistake the implementation method, the task request, or a restatement of the decision for its rationale. If the rationale remains unclear or the relevant source is unavailable, omit `--rationale` rather than inventing one.
89
90
 
90
- For proactively identified decisions, let confidence in the rationale determine the interaction:
91
+ For proactively identified decisions in an active repo, let confidence in the rationale determine the interaction:
91
92
 
92
93
  - **Clear and well-supported** — record the decision and rationale silently at the natural task boundary.
93
94
  - **Plausible but uncertain** — at the task boundary, state the proposed rationale and ask for lightweight confirmation: “I understand the reason for choosing X to be Y. Is that right?” Record after confirmation or correction.
@@ -95,13 +96,35 @@ For proactively identified decisions, let confidence in the rationale determine
95
96
 
96
97
  These questions share the interruption budget below; never ask separate questions for the decision and its rationale. If both are uncertain, combine them into one concise prompt. An explicit request to “add this decision to Primitive” still records immediately with the information supplied—do not delay it to demand rationale.
97
98
 
99
+ ### Inactive repos: approve each deliberate create
100
+
101
+ When passive capture is inactive for the current repo, do not silently turn a durable
102
+ decision into a write. “Inactive” means Prim's effective repo-capture check is false;
103
+ an explicit local `prim.active=false` is always inactive. Every
104
+ `prim decisions create` invocation needs fresh user approval:
105
+
106
+ - An explicit request to record or create that Decision is approval for that one
107
+ invocation; do not ask redundantly.
108
+ - For a proactively identified Decision, present the proposed intent and supported
109
+ rationale, then wait for approval before creating it. A previous approval never
110
+ carries forward to another Decision.
111
+ - After approval, pass Prim's global `--yes` for that invocation:
112
+ `npx --yes @primitive.ai/prim --yes decisions create ...`. The first `--yes` is
113
+ npm's package-install flag; the second is Prim's one-time confirmation.
114
+ - Do not run `prim enable` merely to bypass this boundary or infer permission to
115
+ activate the repo. Activation requires a separate user request (a requested
116
+ `prim setup` counts because setup explicitly activates its current repo). A
117
+ one-time create leaves passive capture inactive; auth approval, npm's
118
+ `npx --yes`, setup's shell-permission grant, and earlier creates do not
119
+ independently change that state.
120
+
98
121
  ### Direct requests: record immediately
99
122
 
100
- When the user asks to record a decision—for example, “add this decision to Primitive”—author it directly. Do not ask for another confirmation.
123
+ When the user asks to record a decision—for example, “add this decision to Primitive”—author it directly. Do not ask for another confirmation. In an inactive repo, the request is the one-time approval: use Prim's `--yes` for that create as described above.
101
124
 
102
125
  ### Clear decisions: record without interrupting
103
126
 
104
- When the user clearly makes a durable fork-in-the-road decision without explicitly asking to record it, record it at the next natural task boundary without asking a redundant confirmation question. Preserve the user's meaning and stated rationale; do not strengthen, broaden, or embellish it. Prefer the governing position over the implementation activity that revealed it.
127
+ When the user clearly makes a durable fork-in-the-road decision without explicitly asking to record it, record it at the next natural task boundary without asking a redundant confirmation question **when passive capture is active**. In an inactive repo, present it and obtain the per-create approval above. Preserve the user's meaning and stated rationale; do not strengthen, broaden, or embellish it. Prefer the governing position over the implementation activity that revealed it.
105
128
 
106
129
  Examples that qualify:
107
130
 
@@ -140,12 +163,18 @@ Outside onboarding, before creating a proactively identified decision, read rece
140
163
  npx --yes @primitive.ai/prim decisions recent --limit 20
141
164
  ```
142
165
 
143
- If an equivalent decision is already present, do not create or suggest it again. After this duplicate check—or after the user confirms an onboarding proposal—author the decision directly:
166
+ If an equivalent decision is already present, do not create or suggest it again. After this duplicate check—or after the user confirms an onboarding proposal—author the decision directly. If the repo is inactive, that confirmation authorizes Prim's `--yes` only for the approved create:
144
167
 
145
168
  ```
146
169
  npx --yes @primitive.ai/prim decisions create --intent "Adopt prosemirror-collab over Yjs" --area data --rationale "Server-authoritative ordering" --alternatives "Yjs,Automerge"
147
170
  ```
148
171
 
172
+ Inactive-repo form after approval:
173
+
174
+ ```
175
+ npx --yes @primitive.ai/prim --yes decisions create --intent "Adopt prosemirror-collab over Yjs" --area data --rationale "Server-authoritative ordering" --alternatives "Yjs,Automerge"
176
+ ```
177
+
149
178
  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). Omit `--files` for broad directions that should not immediately participate in file-based Conflict Gates. STDOUT is the created identity `{ decisionId, shortId, createdAt }`; STDERR prints `[prim] created dec_<short>.` — pass that `dec_<short>` straight into `decisions show` / `cascade` / `confirm`.
150
179
 
151
180
  ### Inferred decisions: finish first, then optionally ask once
@@ -227,11 +256,11 @@ Examples:
227
256
  - **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.
228
257
  - **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.
229
258
  - **Reconcile bypasses are single-use and short-lived.** One bypass clears your *next* edit to the governed file; it is not a standing override.
230
- - **Capture of coding activity is automatic; never inject moves by hand.** If decisions aren't showing up, check that the session hooks are installed (`claude status` / `codex status` / `hermes status`) and the daemon is running. Deliberately authoring a higher-order fork—either at the user's request or under the conservative proactive policy above—is a separate, supported path through `decisions create`.
259
+ - **Capture of coding activity is automatic only in active repos; never inject moves by hand.** If an active repo's decisions aren't showing up, check that the session hooks are installed (`claude status` / `codex status` / `hermes status`) and the daemon is running. Deliberately authoring a higher-order fork—either at the user's request or under the conservative proactive policy above—is a separate, supported path through `decisions create`; in an inactive repo, honor the per-invocation approval boundary above.
231
260
  - **Don't fabricate rationale on a confirmation.** If you don't know why a decision was made, say so rather than guessing.
232
261
 
233
262
  ## After each task
234
263
 
235
- If the user clearly made a durable fork-in-the-road decision, check recent decisions and record it now if it is not already present. If a strong higher-order decision was only inferred, decide whether it clears the interruption budget; if so, ask the single concise confirmation question after reporting the completed work. Otherwise say nothing about Primitive.
264
+ If the user clearly made a durable fork-in-the-road decision, check recent decisions and record it now if it is not already present when capture is active. When capture is inactive, record it only if the user explicitly requested that create; otherwise present it for the per-invocation approval above. If a strong higher-order decision was only inferred, decide whether it clears the interruption budget; if so, ask the single concise confirmation question after reporting the completed work. Otherwise say nothing about Primitive.
236
265
 
237
266
  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.
@@ -1,83 +1,11 @@
1
- // src/lib/activation.ts
2
- import { execFileSync as execFileSync2 } from "child_process";
3
- import { existsSync, readFileSync } from "fs";
4
- import { homedir } from "os";
5
- import { join } from "path";
6
-
7
- // src/lib/git.ts
8
- import { execFileSync } from "child_process";
9
- function gitToplevel(cwd) {
10
- try {
11
- return execFileSync("git", ["rev-parse", "--show-toplevel"], {
12
- cwd,
13
- encoding: "utf-8",
14
- stdio: ["ignore", "pipe", "ignore"]
15
- }).trim();
16
- } catch {
17
- return null;
18
- }
19
- }
20
-
21
- // src/lib/activation.ts
22
- var PRIM_ACTIVE_KEY = "prim.active";
23
- var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
24
- var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
25
- function repoActiveFlag(cwd) {
26
- try {
27
- const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
28
- cwd,
29
- encoding: "utf-8",
30
- stdio: ["ignore", "pipe", "ignore"]
31
- }).trim();
32
- if (value === "true") return "true";
33
- if (value === "false") return "false";
34
- return void 0;
35
- } catch {
36
- return void 0;
37
- }
38
- }
39
- function setRepoActive(cwd, active) {
40
- execFileSync2("git", ["config", "--local", PRIM_ACTIVE_KEY, active ? "true" : "false"], {
41
- cwd,
42
- stdio: ["ignore", "ignore", "pipe"]
43
- });
44
- }
45
- function activateRepoBestEffort(cwd) {
46
- try {
47
- setRepoActive(cwd, true);
48
- } catch {
49
- }
50
- }
51
- function hasProjectPrimInstall(cwd) {
52
- const root = gitToplevel(cwd);
53
- if (root === null || root === homedir()) return false;
54
- for (const rel of PROJECT_INSTALL_FILES) {
55
- const path = join(root, rel);
56
- if (existsSync(path)) {
57
- try {
58
- const content = readFileSync(path, "utf-8");
59
- if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
60
- } catch {
61
- }
62
- }
63
- }
64
- return false;
65
- }
66
- function isRepoActiveForCapture(cwd) {
67
- const flag = repoActiveFlag(cwd);
68
- if (flag === "true") return true;
69
- if (flag === "false") return false;
70
- return hasProjectPrimInstall(cwd);
71
- }
72
-
73
1
  // src/lib/bin-cache.ts
74
2
  import { chmodSync, mkdirSync, renameSync, writeFileSync } from "fs";
75
- import { homedir as homedir2 } from "os";
76
- import { join as join3 } from "path";
3
+ import { homedir } from "os";
4
+ import { join as join2 } from "path";
77
5
 
78
6
  // src/lib/bin-path.ts
79
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
80
- import { dirname, isAbsolute, join as join2 } from "path";
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { dirname, isAbsolute, join } from "path";
81
9
  import { fileURLToPath } from "url";
82
10
  var PKG_NAME = "@primitive.ai/prim";
83
11
  var ROOT_WALK_LIMIT = 6;
@@ -91,10 +19,10 @@ function locateRoot() {
91
19
  }
92
20
  let dir = dirname(fileURLToPath(import.meta.url));
93
21
  for (let depth = 0; depth < ROOT_WALK_LIMIT; depth++) {
94
- const manifestPath = join2(dir, "package.json");
95
- if (existsSync2(manifestPath)) {
22
+ const manifestPath = join(dir, "package.json");
23
+ if (existsSync(manifestPath)) {
96
24
  try {
97
- const manifest = JSON.parse(readFileSync2(manifestPath, "utf-8"));
25
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
98
26
  if (manifest.name === PKG_NAME && manifest.bin) {
99
27
  resolvedRoot = { dir, bin: manifest.bin };
100
28
  return resolvedRoot;
@@ -117,7 +45,7 @@ function binFile(bin) {
117
45
  if (!root || !rel) {
118
46
  return null;
119
47
  }
120
- return isAbsolute(rel) ? rel : join2(root.dir, rel);
48
+ return isAbsolute(rel) ? rel : join(root.dir, rel);
121
49
  }
122
50
  function hookShimCommand(bin, args = "", opts = {}) {
123
51
  const invoke = (cmd) => args ? `${cmd} ${args}` : cmd;
@@ -146,8 +74,8 @@ function commandMatchesBin(command, bin) {
146
74
  // src/lib/bin-cache.ts
147
75
  var CACHED_BINS = ["prim", "prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
148
76
  function binCacheDir() {
149
- const base = process.env.XDG_CACHE_HOME || join3(homedir2(), ".cache");
150
- return join3(base, "prim", "bin");
77
+ const base = process.env.XDG_CACHE_HOME || join2(homedir(), ".cache");
78
+ return join2(base, "prim", "bin");
151
79
  }
152
80
  function writeAtomic(path, content) {
153
81
  const tmp = `${path}.${process.pid}.tmp`;
@@ -165,11 +93,11 @@ function warmBinCache() {
165
93
  const dir = binCacheDir();
166
94
  mkdirSync(dir, { recursive: true, mode: 448 });
167
95
  chmodSync(dir, 448);
168
- writeAtomic(join3(dir, "node"), process.execPath);
96
+ writeAtomic(join2(dir, "node"), process.execPath);
169
97
  for (const bin of CACHED_BINS) {
170
98
  const file = binFile(bin);
171
99
  if (file) {
172
- writeAtomic(join3(dir, bin), file);
100
+ writeAtomic(join2(dir, bin), file);
173
101
  }
174
102
  }
175
103
  } catch {
@@ -177,10 +105,6 @@ function warmBinCache() {
177
105
  }
178
106
 
179
107
  export {
180
- gitToplevel,
181
- setRepoActive,
182
- activateRepoBestEffort,
183
- isRepoActiveForCapture,
184
108
  binFile,
185
109
  hookShimCommand,
186
110
  detachedHookShimCommand,
@@ -0,0 +1,82 @@
1
+ // src/lib/activation.ts
2
+ import { execFileSync as execFileSync2 } from "child_process";
3
+ import { existsSync, readFileSync } from "fs";
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+
7
+ // src/lib/git.ts
8
+ import { execFileSync } from "child_process";
9
+ function gitToplevel(cwd) {
10
+ try {
11
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
12
+ cwd,
13
+ encoding: "utf-8",
14
+ stdio: ["ignore", "pipe", "ignore"]
15
+ }).trim();
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ // src/lib/activation.ts
22
+ var PRIM_ACTIVE_KEY = "prim.active";
23
+ var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
24
+ var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
25
+ function repoActiveFlag(cwd) {
26
+ try {
27
+ const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
28
+ cwd,
29
+ encoding: "utf-8",
30
+ stdio: ["ignore", "pipe", "ignore"]
31
+ }).trim();
32
+ if (value === "true") return "true";
33
+ if (value === "false") return "false";
34
+ return void 0;
35
+ } catch {
36
+ return void 0;
37
+ }
38
+ }
39
+ function setRepoActive(cwd, active) {
40
+ execFileSync2("git", ["config", "--local", PRIM_ACTIVE_KEY, active ? "true" : "false"], {
41
+ cwd,
42
+ stdio: ["ignore", "ignore", "pipe"]
43
+ });
44
+ }
45
+ function activateRepoBestEffort(cwd) {
46
+ try {
47
+ setRepoActive(cwd, true);
48
+ } catch {
49
+ }
50
+ }
51
+ function hasProjectPrimInstall(cwd) {
52
+ const root = gitToplevel(cwd);
53
+ if (root === null || root === homedir()) return false;
54
+ for (const rel of PROJECT_INSTALL_FILES) {
55
+ const path = join(root, rel);
56
+ if (existsSync(path)) {
57
+ try {
58
+ const content = readFileSync(path, "utf-8");
59
+ if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
60
+ } catch {
61
+ }
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ function isRepoActiveForCapture(cwd) {
67
+ const flag = repoActiveFlag(cwd);
68
+ if (flag === "true") return true;
69
+ if (flag === "false") return false;
70
+ return hasProjectPrimInstall(cwd);
71
+ }
72
+ function decisionIngestionStatus(cwd) {
73
+ return isRepoActiveForCapture(cwd) ? "enabled" : "disabled";
74
+ }
75
+
76
+ export {
77
+ gitToplevel,
78
+ setRepoActive,
79
+ activateRepoBestEffort,
80
+ isRepoActiveForCapture,
81
+ decisionIngestionStatus
82
+ };
@@ -6,6 +6,9 @@ import {
6
6
  appendMove,
7
7
  resolveOrg
8
8
  } from "../chunk-5K725K35.js";
9
+ import {
10
+ isRepoActiveForCapture
11
+ } from "../chunk-R5ZJRSLV.js";
9
12
  import "../chunk-TPROV45L.js";
10
13
 
11
14
  // src/hooks/post-commit.ts
@@ -53,20 +56,31 @@ function spawnBackgroundFlush() {
53
56
  stdio: "ignore"
54
57
  }).unref();
55
58
  }
56
- try {
59
+ function runPostCommit() {
60
+ const cwd = git("rev-parse --show-toplevel") ?? process.cwd();
61
+ if (!isRepoActiveForCapture(cwd)) {
62
+ return;
63
+ }
57
64
  const commit = readCommit();
58
65
  if (commit) {
59
- const cwd = git("rev-parse --show-toplevel") ?? process.cwd();
60
66
  const move = toCommitMove(commit, resolveCliVersion(), cwd);
61
67
  const { orgId } = resolveOrg({ sessionId: "", cwd });
62
68
  appendMove(move, orgId);
63
69
  spawnBackgroundFlush();
64
70
  }
65
- } catch (err) {
66
- if (process.env.PRIM_HOOK_DEBUG) {
67
- const detail = err instanceof Error ? err.message : String(err);
68
- process.stderr.write(`[prim-post-commit] capture failed: ${detail}
71
+ }
72
+ if (!process.env.VITEST) {
73
+ try {
74
+ runPostCommit();
75
+ } catch (err) {
76
+ if (process.env.PRIM_HOOK_DEBUG) {
77
+ const detail = err instanceof Error ? err.message : String(err);
78
+ process.stderr.write(`[prim-post-commit] capture failed: ${detail}
69
79
  `);
80
+ }
70
81
  }
82
+ process.exit(0);
71
83
  }
72
- process.exit(0);
84
+ export {
85
+ runPostCommit
86
+ };
@@ -18,9 +18,11 @@ import {
18
18
  getOrCreateWorkspaceId
19
19
  } from "../chunk-IMAIBPUC.js";
20
20
  import {
21
- isRepoActiveForCapture,
22
21
  warmBinCache
23
- } from "../chunk-F7O7V6ZM.js";
22
+ } from "../chunk-HSZPTVKU.js";
23
+ import {
24
+ isRepoActiveForCapture
25
+ } from "../chunk-R5ZJRSLV.js";
24
26
  import {
25
27
  getClient
26
28
  } from "../chunk-TPROV45L.js";
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- isRepoActiveForCapture,
4
3
  warmBinCache
5
- } from "../chunk-F7O7V6ZM.js";
4
+ } from "../chunk-HSZPTVKU.js";
5
+ import {
6
+ isRepoActiveForCapture
7
+ } from "../chunk-R5ZJRSLV.js";
6
8
  import {
7
9
  getClient,
8
10
  getSiteUrl
@@ -24,9 +24,11 @@ import {
24
24
  getOrCreateWorkspaceId
25
25
  } from "../chunk-IMAIBPUC.js";
26
26
  import {
27
- isRepoActiveForCapture,
28
27
  warmBinCache
29
- } from "../chunk-F7O7V6ZM.js";
28
+ } from "../chunk-HSZPTVKU.js";
29
+ import {
30
+ isRepoActiveForCapture
31
+ } from "../chunk-R5ZJRSLV.js";
30
32
  import "../chunk-TPROV45L.js";
31
33
  import {
32
34
  normalizeEnvelope,
@@ -14,9 +14,11 @@ import {
14
14
  } from "../chunk-IMAIBPUC.js";
15
15
  import {
16
16
  binFile,
17
- isRepoActiveForCapture,
18
17
  warmBinCache
19
- } from "../chunk-F7O7V6ZM.js";
18
+ } from "../chunk-HSZPTVKU.js";
19
+ import {
20
+ isRepoActiveForCapture
21
+ } from "../chunk-R5ZJRSLV.js";
20
22
  import {
21
23
  getSiteUrl
22
24
  } from "../chunk-TPROV45L.js";
package/dist/index.js CHANGED
@@ -29,15 +29,19 @@ import {
29
29
  inspectWorkspaceId
30
30
  } from "./chunk-IMAIBPUC.js";
31
31
  import {
32
- activateRepoBestEffort,
33
32
  binFile,
34
33
  commandMatchesBin,
35
34
  detachedHookShimCommand,
36
- gitToplevel,
37
35
  hookShimCommand,
38
- setRepoActive,
39
36
  warmBinCache
40
- } from "./chunk-F7O7V6ZM.js";
37
+ } from "./chunk-HSZPTVKU.js";
38
+ import {
39
+ activateRepoBestEffort,
40
+ decisionIngestionStatus,
41
+ gitToplevel,
42
+ isRepoActiveForCapture,
43
+ setRepoActive
44
+ } from "./chunk-R5ZJRSLV.js";
41
45
  import {
42
46
  HttpError,
43
47
  REFRESH_TOKEN_PATH,
@@ -1703,6 +1707,12 @@ var HEALTHY_POLL_MS = 250;
1703
1707
  var EXIT_OK2 = 0;
1704
1708
  var EXIT_NOT_RUNNING = 2;
1705
1709
  var EXIT_BOOTING = 3;
1710
+ function formatDaemonLifecycleMessage(message, decisionIngestion) {
1711
+ return `${message} \xB7 Decision ingestion ${decisionIngestion}`;
1712
+ }
1713
+ function formatCurrentDaemonLifecycleMessage(message) {
1714
+ return formatDaemonLifecycleMessage(message, decisionIngestionStatus(process.cwd()));
1715
+ }
1706
1716
  var MAX_HEALTH_ERROR_LENGTH = 240;
1707
1717
  function boundedHealthError(value) {
1708
1718
  if (!value) return void 0;
@@ -1819,8 +1829,10 @@ async function detachedDaemonStart(opts) {
1819
1829
  if (existing?.alive) {
1820
1830
  const snapshot = await verifiedPid(existing);
1821
1831
  if (snapshot) {
1822
- process.stderr.write(`[prim] daemon already running (pid=${existing.pid})
1823
- `);
1832
+ process.stderr.write(
1833
+ `${formatCurrentDaemonLifecycleMessage(`[prim] daemon already running (pid=${existing.pid})`)}
1834
+ `
1835
+ );
1824
1836
  console.log(JSON.stringify({ started: false, pid: existing.pid }, null, 2));
1825
1837
  return;
1826
1838
  }
@@ -1876,7 +1888,7 @@ async function detachedDaemonStart(opts) {
1876
1888
  if (live) {
1877
1889
  const after = readPidfile();
1878
1890
  process.stderr.write(
1879
- `[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH})
1891
+ `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon started (pid=${after?.pid ?? "?"}, socket=${SOCK_PATH})`)}
1880
1892
  `
1881
1893
  );
1882
1894
  console.log(JSON.stringify({ started: true, pid: after?.pid }, null, 2));
@@ -1966,7 +1978,7 @@ async function macDaemonStart(forceRestart = false) {
1966
1978
  if (healthy) {
1967
1979
  const verb = result.action === "none" ? "already running" : "started";
1968
1980
  process.stderr.write(
1969
- `[prim] \u2713 daemon ${verb} under launchd (pid=${snapshot?.pid ?? result.service.pid ?? "?"})
1981
+ `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ${verb} under launchd (pid=${snapshot?.pid ?? result.service.pid ?? "?"})`)}
1970
1982
  `
1971
1983
  );
1972
1984
  } else {
@@ -2140,28 +2152,23 @@ function classifyLaunchdStatus(service, responding, snapshot, disabled) {
2140
2152
  exitCode: EXIT_OK2
2141
2153
  };
2142
2154
  }
2143
- function writeLiveSnapshot(snapshot, supervised = false) {
2155
+ function formatDaemonSnapshotMessage(snapshot, supervised, decisionIngestion) {
2144
2156
  if (!snapshot) {
2145
- process.stderr.write(
2146
- supervised ? "[prim] \u2713 daemon live under launchd (no snapshot)\n" : "[prim] \u2713 daemon live\n"
2147
- );
2148
- return;
2157
+ return supervised ? `[prim] \u2713 daemon live, Decision ingestion ${decisionIngestion} under launchd (no snapshot)` : `[prim] \u2713 daemon live, Decision ingestion ${decisionIngestion}`;
2149
2158
  }
2150
2159
  const team = snapshot.onlineNames !== void 0 ? ` \xB7 team: ${formatTeammates(snapshot.onlineNames, Number.POSITIVE_INFINITY)}` : "";
2151
2160
  if (snapshot.healthy === false) {
2152
2161
  const reason = daemonDegradedReason(snapshot);
2153
- process.stderr.write(
2154
- `[prim] \u2717 daemon unhealthy${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid}${team}${reason ? ` \xB7 ${reason}` : ""}
2155
- `
2156
- );
2157
- return;
2162
+ return `[prim] \u2717 daemon unhealthy${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid}${team}${reason ? ` \xB7 ${reason}` : ""}`;
2158
2163
  }
2159
- process.stderr.write(
2160
- `[prim] \u2713 daemon live${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(
2161
- snapshot.uptimeMs / 1e3
2162
- )}s \xB7 session=${snapshot.sessionId}${team}
2163
- `
2164
- );
2164
+ return `[prim] \u2713 daemon live, Decision ingestion ${decisionIngestion}${supervised ? " under launchd" : ""} \xB7 pid=${snapshot.pid} \xB7 uptime=${Math.round(
2165
+ snapshot.uptimeMs / 1e3
2166
+ )}s \xB7 session=${snapshot.sessionId}${team}`;
2167
+ }
2168
+ function writeLiveSnapshot(snapshot, supervised = false) {
2169
+ const decisionIngestion = snapshot?.healthy === false ? "disabled" : decisionIngestionStatus(process.cwd());
2170
+ process.stderr.write(`${formatDaemonSnapshotMessage(snapshot, supervised, decisionIngestion)}
2171
+ `);
2165
2172
  }
2166
2173
  async function detachedDaemonStatus() {
2167
2174
  const pid = readPidfile();
@@ -2262,8 +2269,10 @@ async function daemonEnsure() {
2262
2269
  if (result.state === "disabled") {
2263
2270
  process.stderr.write("[prim] daemon remains explicitly disabled\n");
2264
2271
  } else if (result.state === "running") {
2265
- process.stderr.write(`[prim] \u2713 daemon ensured under launchd (${result.action})
2266
- `);
2272
+ process.stderr.write(
2273
+ `${formatCurrentDaemonLifecycleMessage(`[prim] \u2713 daemon ensured under launchd (${result.action})`)}
2274
+ `
2275
+ );
2267
2276
  } else {
2268
2277
  process.stderr.write(`[prim] \u2717 daemon ensure failed; see ${LOG_PATH}
2269
2278
  `);
@@ -2999,9 +3008,29 @@ function formatShowJson(result) {
2999
3008
  return JSON.stringify(result, null, 2);
3000
3009
  }
3001
3010
 
3011
+ // src/lib/confirmation.ts
3012
+ function isNonInteractive(globals) {
3013
+ return Boolean(globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE);
3014
+ }
3015
+ async function askConfirmation(question, output = process.stdout) {
3016
+ if (!process.stdin.isTTY) return false;
3017
+ const { createInterface: createInterface2 } = await import("readline/promises");
3018
+ const rl = createInterface2({ input: process.stdin, output });
3019
+ try {
3020
+ const answer = await rl.question(`${question} [y/N] `);
3021
+ const normalized = answer.trim().toLowerCase();
3022
+ return normalized === "y" || normalized === "yes";
3023
+ } finally {
3024
+ rl.close();
3025
+ }
3026
+ }
3027
+
3002
3028
  // src/commands/decisions.ts
3003
3029
  var EXIT_NOT_FOUND = 4;
3004
3030
  var EXIT_USAGE = 2;
3031
+ var CREATE_INACTIVE_PROMPT = "[prim] Decision ingestion is disabled here. Create this one Decision without enabling passive ingestion?";
3032
+ var CREATE_INACTIVE_APPROVED = "[prim] one-time Decision creation approved; passive ingestion remains disabled here";
3033
+ var CREATE_INACTIVE_REJECTED = "[prim] decision not created: Decision ingestion is disabled here; rerun with prim's --yes to approve this one Decision, or run `prim enable` in a Git project";
3005
3034
  var splitList = (value) => (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
3006
3035
  function registerDecisionsCommands(program2) {
3007
3036
  const decisions = program2.command("decisions").description("Inspect the project Decision Graph");
@@ -3081,7 +3110,19 @@ function registerDecisionsCommands(program2) {
3081
3110
  ).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(
3082
3111
  "--files <paths>",
3083
3112
  "Comma-separated repo-relative paths this decision governs (the files Conflict Gates would check \u2014 not currently enabled)"
3084
- ).action(async (opts) => {
3113
+ ).action(async (opts, command) => {
3114
+ if (!isRepoActiveForCapture(process.cwd())) {
3115
+ const globals = command.optsWithGlobals();
3116
+ const nonInteractive = isNonInteractive(globals);
3117
+ const approved = Boolean(globals.yes) || !nonInteractive && await askConfirmation(CREATE_INACTIVE_PROMPT, process.stderr);
3118
+ if (!approved) {
3119
+ console.error(CREATE_INACTIVE_REJECTED);
3120
+ console.log(JSON.stringify({ ok: false, error: "prim_inactive" }, null, 2));
3121
+ process.exitCode = EXIT_USAGE;
3122
+ return;
3123
+ }
3124
+ console.error(CREATE_INACTIVE_APPROVED);
3125
+ }
3085
3126
  const request = {
3086
3127
  intent: opts.intent,
3087
3128
  kind: opts.kind,
@@ -3243,10 +3284,11 @@ function classifyDaemonHealth(snapshot, options = {}) {
3243
3284
  if (snapshot.healthy !== true) {
3244
3285
  return { name: "daemon", status: "fail", detail: "health state is not ready" };
3245
3286
  }
3287
+ const ingestionStatus = options.ingestionStatus ?? decisionIngestionStatus(process.cwd());
3246
3288
  return {
3247
3289
  name: "daemon",
3248
3290
  status: "ok",
3249
- detail: `supervised and healthy${snapshot.version ? ` \xB7 v${snapshot.version}` : ""}`
3291
+ detail: `supervised and healthy${snapshot.version ? ` \xB7 v${snapshot.version}` : ""} \xB7 Decision ingestion ${ingestionStatus}`
3250
3292
  };
3251
3293
  }
3252
3294
  async function checkDaemon() {
@@ -3925,18 +3967,6 @@ function detectHusky(gitRoot) {
3925
3967
  function containsPrimHook(content, binName = PRE_COMMIT.binName) {
3926
3968
  return content.includes(binName);
3927
3969
  }
3928
- async function askConfirmation(question) {
3929
- if (!process.stdin.isTTY) return false;
3930
- const { createInterface: createInterface2 } = await import("readline/promises");
3931
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
3932
- try {
3933
- const answer = await rl.question(`${question} [y/N] `);
3934
- const normalized = answer.trim().toLowerCase();
3935
- return normalized === "y" || normalized === "yes";
3936
- } finally {
3937
- rl.close();
3938
- }
3939
- }
3940
3970
  function installToHusky(gitRoot, spec = PRE_COMMIT) {
3941
3971
  const hookPath = resolve2(gitRoot, ".husky", spec.hookName);
3942
3972
  const existed = existsSync7(hookPath);
@@ -4154,9 +4184,7 @@ function registerHooksCommands(program2) {
4154
4184
  return;
4155
4185
  }
4156
4186
  const globals = command.optsWithGlobals();
4157
- const nonInteractive = Boolean(
4158
- globals.nonInteractive || process.env.CI || process.env.PRIM_NON_INTERACTIVE
4159
- );
4187
+ const nonInteractive = isNonInteractive(globals);
4160
4188
  const gitRoot = getGitRoot();
4161
4189
  if (opts.target === "husky") return installHooks(gitRoot, "husky");
4162
4190
  if (opts.target === "git-hooks") return installHooks(gitRoot, "git-hooks");
@@ -5179,11 +5207,12 @@ async function renderStatusline() {
5179
5207
  }
5180
5208
  return `primitive ${version} (daemon: starting)`;
5181
5209
  }
5210
+ const ingestionStatus = decisionIngestionStatus(process.cwd());
5182
5211
  if (snapshot.envMismatch) {
5183
- return `primitive ${version} (daemon: live \xB7 presence: other env)`;
5212
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
5184
5213
  }
5185
5214
  if (snapshot.presenceStale) {
5186
- return `primitive ${version} (daemon: live \xB7 presence: stale)`;
5215
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
5187
5216
  }
5188
5217
  let team;
5189
5218
  if (snapshot.onlineTeammates !== void 0) {
@@ -5195,7 +5224,7 @@ async function renderStatusline() {
5195
5224
  } else {
5196
5225
  team = "team: \u2014";
5197
5226
  }
5198
- return `primitive ${version} (daemon: live \xB7 ${team})`;
5227
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
5199
5228
  }
5200
5229
  function registerStatuslineCommands(program2) {
5201
5230
  program2.command("statusline").description("Render the Claude Code statusLine for the prim companion daemon").action(async () => {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands/statusline.ts
4
- import { readFileSync as readFileSync4 } from "fs";
4
+ import { readFileSync as readFileSync5 } from "fs";
5
5
  import { dirname as dirname4, resolve as resolve2 } from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
@@ -132,14 +132,77 @@ function daemonRequest(method, params = {}, opts = {}) {
132
132
  });
133
133
  }
134
134
 
135
+ // src/lib/activation.ts
136
+ import { execFileSync as execFileSync2 } from "child_process";
137
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
138
+ import { homedir as homedir3 } from "os";
139
+ import { join as join4 } from "path";
140
+
141
+ // src/lib/git.ts
142
+ import { execFileSync } from "child_process";
143
+ function gitToplevel(cwd) {
144
+ try {
145
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
146
+ cwd,
147
+ encoding: "utf-8",
148
+ stdio: ["ignore", "pipe", "ignore"]
149
+ }).trim();
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ // src/lib/activation.ts
156
+ var PRIM_ACTIVE_KEY = "prim.active";
157
+ var PROJECT_INSTALL_FILES = [".claude/settings.json", ".codex/hooks.json"];
158
+ var PRIM_HOOK_BINS = ["prim-hook", "prim-pre-tool-use", "prim-post-tool-use"];
159
+ function repoActiveFlag(cwd) {
160
+ try {
161
+ const value = execFileSync2("git", ["config", "--get", PRIM_ACTIVE_KEY], {
162
+ cwd,
163
+ encoding: "utf-8",
164
+ stdio: ["ignore", "pipe", "ignore"]
165
+ }).trim();
166
+ if (value === "true") return "true";
167
+ if (value === "false") return "false";
168
+ return void 0;
169
+ } catch {
170
+ return void 0;
171
+ }
172
+ }
173
+ function hasProjectPrimInstall(cwd) {
174
+ const root = gitToplevel(cwd);
175
+ if (root === null || root === homedir3()) return false;
176
+ for (const rel of PROJECT_INSTALL_FILES) {
177
+ const path = join4(root, rel);
178
+ if (existsSync3(path)) {
179
+ try {
180
+ const content = readFileSync3(path, "utf-8");
181
+ if (PRIM_HOOK_BINS.some((bin) => content.includes(bin))) return true;
182
+ } catch {
183
+ }
184
+ }
185
+ }
186
+ return false;
187
+ }
188
+ function isRepoActiveForCapture(cwd) {
189
+ const flag = repoActiveFlag(cwd);
190
+ if (flag === "true") return true;
191
+ if (flag === "false") return false;
192
+ return hasProjectPrimInstall(cwd);
193
+ }
194
+ function decisionIngestionStatus(cwd) {
195
+ return isRepoActiveForCapture(cwd) ? "enabled" : "disabled";
196
+ }
197
+
135
198
  // src/lib/bin-cache.ts
136
199
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync3, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "fs";
137
- import { homedir as homedir3 } from "os";
138
- import { join as join5 } from "path";
200
+ import { homedir as homedir4 } from "os";
201
+ import { join as join6 } from "path";
139
202
 
140
203
  // src/lib/bin-path.ts
141
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
142
- import { dirname as dirname3, isAbsolute, join as join4 } from "path";
204
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
205
+ import { dirname as dirname3, isAbsolute, join as join5 } from "path";
143
206
  import { fileURLToPath } from "url";
144
207
  var PKG_NAME = "@primitive.ai/prim";
145
208
  var NPX_FALLBACK = `npx --yes -p ${PKG_NAME}@latest`;
@@ -218,7 +281,7 @@ function readPackageVersion() {
218
281
  ];
219
282
  for (const path of candidates) {
220
283
  try {
221
- const pkg = JSON.parse(readFileSync4(path, "utf-8"));
284
+ const pkg = JSON.parse(readFileSync5(path, "utf-8"));
222
285
  if (pkg.version) {
223
286
  return pkg.version;
224
287
  }
@@ -259,11 +322,12 @@ async function renderStatusline() {
259
322
  }
260
323
  return `primitive ${version} (daemon: starting)`;
261
324
  }
325
+ const ingestionStatus = decisionIngestionStatus(process.cwd());
262
326
  if (snapshot.envMismatch) {
263
- return `primitive ${version} (daemon: live \xB7 presence: other env)`;
327
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: other env)`;
264
328
  }
265
329
  if (snapshot.presenceStale) {
266
- return `primitive ${version} (daemon: live \xB7 presence: stale)`;
330
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 presence: stale)`;
267
331
  }
268
332
  let team;
269
333
  if (snapshot.onlineTeammates !== void 0) {
@@ -275,7 +339,7 @@ async function renderStatusline() {
275
339
  } else {
276
340
  team = "team: \u2014";
277
341
  }
278
- return `primitive ${version} (daemon: live \xB7 ${team})`;
342
+ return `primitive ${version} (daemon: live, Decision ingestion ${ingestionStatus} \xB7 ${team})`;
279
343
  }
280
344
 
281
345
  // src/statusline-main.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitive.ai/prim",
3
- "version": "0.1.0-alpha.48",
3
+ "version": "0.1.0-alpha.49",
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",