@primitive.ai/prim 0.1.0-alpha.19 → 0.1.0-alpha.20
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 +80 -37
- package/SKILL.md +40 -183
- package/dist/{chunk-6SIEWWUL.js → chunk-26VA3ADF.js} +1 -3
- package/dist/{chunk-TPQ3X244.js → chunk-E5UZXMZL.js} +2 -31
- package/dist/daemon/server.js +1 -1
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/hooks/pre-commit.js +4 -157
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/index.js +22 -431
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @primitive.ai/prim
|
|
2
2
|
|
|
3
|
-
The official CLI for [Primitive](https://getprimitive.ai)
|
|
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
7
|
|
|
5
8
|
> [!WARNING]
|
|
6
9
|
> This project is in **alpha**. Commands and APIs may change between releases.
|
|
@@ -22,16 +25,21 @@ npx @primitive.ai/prim
|
|
|
22
25
|
## Quick Start
|
|
23
26
|
|
|
24
27
|
```bash
|
|
25
|
-
# Authenticate via browser (WorkOS OAuth)
|
|
28
|
+
# 1. Authenticate via browser (WorkOS OAuth)
|
|
26
29
|
prim auth login
|
|
27
30
|
|
|
28
|
-
#
|
|
29
|
-
prim
|
|
31
|
+
# 2. Wire the session hooks (decision capture + conflict gate + presence)
|
|
32
|
+
prim claude install # or: prim codex install
|
|
30
33
|
|
|
31
|
-
#
|
|
34
|
+
# 3. Start the companion daemon (latency + team presence)
|
|
35
|
+
prim daemon start
|
|
36
|
+
|
|
37
|
+
# 4. Install the git hooks (pre-commit decision check + post-commit capture)
|
|
32
38
|
prim hooks install
|
|
33
39
|
```
|
|
34
40
|
|
|
41
|
+
An AI coding agent can drive the entire setup itself — see [`setup.md`](./setup.md).
|
|
42
|
+
|
|
35
43
|
## Commands
|
|
36
44
|
|
|
37
45
|
### Auth
|
|
@@ -43,57 +51,92 @@ prim auth clear # Remove saved tokens
|
|
|
43
51
|
prim auth status # Check authentication status
|
|
44
52
|
```
|
|
45
53
|
|
|
46
|
-
###
|
|
54
|
+
### Session integration
|
|
47
55
|
|
|
48
|
-
|
|
56
|
+
Wires the agent's session hooks so the decisions you make are captured into the
|
|
57
|
+
graph, conflicting edits are gated, and presence is reported. Each hook
|
|
58
|
+
self-resolves the CLI at run time (PATH, then a local install, then
|
|
59
|
+
`npx --yes @latest`), so it keeps working with no global install.
|
|
49
60
|
|
|
50
61
|
```bash
|
|
51
|
-
prim
|
|
52
|
-
prim
|
|
53
|
-
prim spec get <id> # Show spec details
|
|
54
|
-
prim spec get <id> --text-only # Print raw spec text
|
|
55
|
-
prim spec update <id> --file spec.md # Update spec from file
|
|
56
|
-
prim spec update <id> --name "New" # Rename a spec
|
|
57
|
-
prim spec sync <id> # Trigger spec-to-project sync
|
|
58
|
-
prim spec map <id> -p "src/auth/**" # Map file patterns to a spec
|
|
59
|
-
prim spec unmap <id> # Clear all file patterns
|
|
60
|
-
prim spec unmap <id> -p "src/auth/**" # Remove specific pattern
|
|
61
|
-
prim spec auto-map <id> # Auto-detect file patterns
|
|
62
|
+
prim claude install # Install Claude Code hooks (uninstall / status)
|
|
63
|
+
prim codex install # Install OpenAI Codex hooks (uninstall / status)
|
|
62
64
|
```
|
|
63
65
|
|
|
64
|
-
###
|
|
66
|
+
### Daemon
|
|
67
|
+
|
|
68
|
+
A long-lived companion process that accelerates the in-session decision checks
|
|
69
|
+
and powers the "team: N online" presence count. Optional — hooks fall back to
|
|
70
|
+
direct calls if it is down.
|
|
65
71
|
|
|
66
72
|
```bash
|
|
67
|
-
prim
|
|
68
|
-
prim context list --scope project # Filter by scope
|
|
69
|
-
prim context list --project-id <id> # List contexts for a project
|
|
70
|
-
prim context get <id> # Get context details
|
|
71
|
-
prim context create -s project -n "Name" # Create a context
|
|
72
|
-
prim context create -s project -n "Name" --file path/to/file
|
|
73
|
-
prim context update <id> --name "New" # Update a context
|
|
74
|
-
prim context delete <id> # Delete a context
|
|
75
|
-
prim context link <id> --project <pid> # Link context to project
|
|
76
|
-
prim context unlink <id> --project <pid> # Unlink context from project
|
|
73
|
+
prim daemon start # start (stop / restart / status)
|
|
77
74
|
```
|
|
78
75
|
|
|
79
|
-
###
|
|
76
|
+
### Decisions
|
|
77
|
+
|
|
78
|
+
Read and respond to the decision graph.
|
|
80
79
|
|
|
81
80
|
```bash
|
|
82
|
-
prim
|
|
83
|
-
prim
|
|
84
|
-
prim
|
|
81
|
+
prim decisions recent # Recent decisions feed
|
|
82
|
+
prim decisions show <id> # Drill into one decision
|
|
83
|
+
prim decisions cascade <id> # Blast radius of a decision
|
|
84
|
+
prim decisions check --files <…> # Active decisions referencing files (warn-only)
|
|
85
|
+
prim decisions confirm <id> # Answer a rationale-confirmation prompt
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`<id>` accepts a full decision ID or its short ID. STDOUT is machine-readable
|
|
89
|
+
JSON; human-readable status goes to STDERR.
|
|
90
|
+
|
|
91
|
+
### Reconcile
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
prim reconcile <id> # Mint a single-use bypass for a decision a gate flagged
|
|
85
95
|
```
|
|
86
96
|
|
|
87
97
|
### Hooks
|
|
88
98
|
|
|
89
99
|
```bash
|
|
90
|
-
prim hooks install
|
|
91
|
-
prim hooks uninstall
|
|
100
|
+
prim hooks install # Install git hooks (pre-commit decision check + post-commit capture)
|
|
101
|
+
prim hooks uninstall # Remove the prim git hooks
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The pre-commit hook checks staged files against the live decision graph
|
|
105
|
+
(warn-only — it never blocks the commit). The post-commit hook records each
|
|
106
|
+
commit as a capture boundary for classification. Supports
|
|
107
|
+
[Husky](https://typicode.github.io/husky/) — `prim hooks install` detects Husky
|
|
108
|
+
and offers to install into `.husky/`.
|
|
109
|
+
|
|
110
|
+
### Presence statusline
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
prim statusline # Render the team-presence statusline (reads the daemon)
|
|
92
114
|
```
|
|
93
115
|
|
|
94
|
-
|
|
116
|
+
### Session & journal
|
|
117
|
+
|
|
118
|
+
Lower-level plumbing for the capture pipeline — org binding and the local move
|
|
119
|
+
journal. Capture works automatically once the session hooks are installed; these
|
|
120
|
+
are for inspecting and steering it (e.g. multi-org machines).
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
prim session start <id> # Pin a Claude Code session to an org (list / drop <id>)
|
|
124
|
+
prim moves bind # Pin the current directory to an org via .prim/workspace.json (drop)
|
|
125
|
+
prim moves status # Per-bucket pending stats for the local journal
|
|
126
|
+
prim moves tail # Pretty-print recent journal entries
|
|
127
|
+
prim moves flush # Drain the local journals to the server (also runs from hooks)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Skill
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
prim skill install # Install the decision-graph agent guide into your rules file
|
|
134
|
+
prim skill uninstall # Remove the managed block
|
|
135
|
+
prim skill status # Report whether the block is installed
|
|
136
|
+
```
|
|
95
137
|
|
|
96
|
-
|
|
138
|
+
Writes a managed block teaching your agent how to work with the decision graph
|
|
139
|
+
into the rules file it reads (CLAUDE.md, AGENTS.md, .cursor/rules, …).
|
|
97
140
|
|
|
98
141
|
## Development
|
|
99
142
|
|
package/SKILL.md
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prim
|
|
3
|
-
description: Use the prim CLI for Primitive
|
|
3
|
+
description: Use the prim CLI for Primitive's decision graph — passive decision capture, the conflict gate, reconcile, rationale confirmations, and team presence. TRIGGER when the user mentions Primitive, prim, decisions / the decision graph / a conflict gate / reconcile; when an edit is denied or warned by a prior decision; when the repo's package.json depends on @primitive.ai/prim; when configuring Primitive session or git hooks. SKIP when "decision" is unrelated to Primitive, or for unrelated CLIs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Working with the prim CLI
|
|
7
7
|
|
|
8
|
-
`prim` is the official CLI for [Primitive](https://app.getprimitive.ai)
|
|
8
|
+
`prim` is the official CLI for [Primitive](https://app.getprimitive.ai)'s **decision graph**. Use it -- don't reach for shell or curl.
|
|
9
9
|
|
|
10
10
|
## Mental model
|
|
11
11
|
|
|
12
|
-
|
|
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 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.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
A **spec is a kind of context** -- same IDs, same storage. The `npx --yes @primitive.ai/prim spec ...` commands are a focused view onto specs; `npx --yes @primitive.ai/prim context get <id>` works on a spec ID and vice versa. For structured metadata on a spec (review status, root project, sync version, scope, file patterns), use `npx --yes @primitive.ai/prim context get <specId>` -- it returns JSON.
|
|
17
|
-
|
|
18
|
-
`npx --yes @primitive.ai/prim spec list` returns only spec-type contexts. `npx --yes @primitive.ai/prim context list` returns all contexts regardless of type.
|
|
14
|
+
You never invoke capture. It runs automatically through the session hooks installed by `npx --yes @primitive.ai/prim claude install` (Claude Code) or `npx --yes @primitive.ai/prim codex install` (Codex). Your job is to **respond** to the gate, **read** the graph before load-bearing edits, and **answer** the occasional rationale confirmation.
|
|
19
15
|
|
|
20
16
|
## Auth
|
|
21
17
|
|
|
@@ -25,21 +21,18 @@ Three ways to authenticate, in priority order:
|
|
|
25
21
|
|
|
26
22
|
1. **`PRIM_TOKEN` environment variable** -- preferred for agents and CI. Set it before invoking prim and you're done; no interactive flow, no token files.
|
|
27
23
|
2. **`npx --yes @primitive.ai/prim auth set-token <token>`** -- saves a bearer token to `~/.config/prim/token`. Use when the user has a long-lived token in hand.
|
|
28
|
-
3. **`npx --yes @primitive.ai/prim auth login`** -- opens a browser via WorkOS OAuth. **
|
|
24
|
+
3. **`npx --yes @primitive.ai/prim auth login`** -- opens a browser via WorkOS OAuth. **Drive this yourself; do not hand it to the user.** It blocks up to 2 minutes waiting for approval -- that wait is expected, not a failure. The user's only action is clicking "Authorize". Run it in the background, surface the authorize URL it prints on STDERR so the user can click it if the browser didn't open, then poll `auth status` until it exits 0. If it times out before they click, run it again -- never fall back to asking them to run it.
|
|
29
25
|
|
|
30
|
-
The CLI auto-refreshes
|
|
26
|
+
The CLI auto-refreshes a still-valid session from the stored refresh token (proactively ~60s before expiry, and again on a 401), so a short access-token "expires in 2m" is normal -- not a reason to re-authenticate or warn the user. Only an absent refresh token, or an explicit `Authentication expired. Run prim auth login to re-authenticate.`, warrants a re-login -- which you then drive yourself, per the above. Relay that message if it appears.
|
|
31
27
|
|
|
32
28
|
## Ground rules
|
|
33
29
|
|
|
34
|
-
1.
|
|
35
|
-
2.
|
|
36
|
-
3.
|
|
30
|
+
1. Every command accepts `--help`. When unsure of flags, run `npx --yes @primitive.ai/prim <cmd> --help` rather than guessing.
|
|
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.
|
|
37
33
|
|
|
38
|
-
##
|
|
34
|
+
## Heed the conflict gate
|
|
39
35
|
|
|
40
|
-
Separate from specs, prim passively captures the decisions you make during a coding session -- which library, which pattern, which config value -- into a queryable decision graph, and actively **gates** edits that would conflict with a load-bearing prior decision. Capture and the gate run automatically through the session hooks installed by `npx --yes @primitive.ai/prim claude install` (Claude Code) or `npx --yes @primitive.ai/prim codex install` (Codex). You never invoke capture; you *respond* to the gate and *read* the graph.
|
|
41
|
-
|
|
42
|
-
### Heed the conflict gate
|
|
43
36
|
Before an edit (Claude Code: Edit/Write/MultiEdit; Codex: apply_patch) a PreToolUse hook scores the target file against the graph:
|
|
44
37
|
|
|
45
38
|
- **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.
|
|
@@ -48,12 +41,14 @@ Before an edit (Claude Code: Edit/Write/MultiEdit; Codex: apply_patch) a PreTool
|
|
|
48
41
|
|
|
49
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.
|
|
50
43
|
|
|
51
|
-
|
|
44
|
+
## Read the graph before large or load-bearing edits
|
|
45
|
+
|
|
52
46
|
- `npx --yes @primitive.ai/prim decisions check --files "src/a.ts,src/b.ts"` -- which active decisions reference the files you're about to touch (comma-separated paths, one `--files` value). Run it before a big change.
|
|
53
47
|
- `npx --yes @primitive.ai/prim decisions recent` -- the team's recent decisions, each row badged by author and agent (`Your Claude Code` / `Your Codex`); `--limit <n>` and `--since <dur>` narrow it.
|
|
54
48
|
- `npx --yes @primitive.ai/prim decisions show <idOrShortId>` and `npx --yes @primitive.ai/prim decisions cascade <idOrShortId>` -- full detail, and the downstream blast radius a change would disturb.
|
|
55
49
|
|
|
56
|
-
|
|
50
|
+
## Reconcile and the verdict footer
|
|
51
|
+
|
|
57
52
|
`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:
|
|
58
53
|
|
|
59
54
|
```
|
|
@@ -62,198 +57,60 @@ The gate fail-opens on its *own* infrastructure errors (no daemon, network blip,
|
|
|
62
57
|
|
|
63
58
|
`N` is the reconciled decision's downstream live-dependent count, shown as `N+` when the server caps it.
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
With the daemon running (`npx --yes @primitive.ai/prim daemon start`), `npx --yes @primitive.ai/prim daemon status` includes the live online count in its STDOUT JSON (when presence is fresh); Claude Code surfaces it in the statusline as `team: N online`. Your captured decisions are attributed to your agent automatically -- no flag required.
|
|
60
|
+
## Answer rationale confirmations
|
|
67
61
|
|
|
68
|
-
|
|
62
|
+
Occasionally the graph asks you (or the user) to confirm *why* a decision was made — a low-friction yes/no, never a paragraph. Answer it with:
|
|
69
63
|
|
|
70
|
-
### Read a spec's current text (do this before any partial edit)
|
|
71
64
|
```
|
|
72
|
-
npx --yes @primitive.ai/prim
|
|
65
|
+
npx --yes @primitive.ai/prim decisions confirm <idOrShortId>
|
|
73
66
|
```
|
|
74
|
-
`npx --yes @primitive.ai/prim spec update <id> --file <path>` replaces the entire body. Fetch first if you're only changing part of it.
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
npx --yes @primitive.ai/prim spec list --project-id <pid> # find the spec for a project
|
|
79
|
-
npx --yes @primitive.ai/prim spec update <id> --file spec.md # replaces spec body
|
|
80
|
-
npx --yes @primitive.ai/prim spec sync <id> # required -- update doesn't apply changes to the project
|
|
81
|
-
```
|
|
82
|
-
`npx --yes @primitive.ai/prim spec sync` is **async**: it returns immediately with `Triggered sync for spec`, then applies in the background. The project isn't updated when the command returns -- surface that to the user.
|
|
83
|
-
|
|
84
|
-
Auto-map runs automatically on the server after every `spec update`. Call `npx --yes @primitive.ai/prim spec auto-map <id>` explicitly only to re-run mapping without changing the spec text.
|
|
85
|
-
|
|
86
|
-
### Map files to a spec (so pre-commit auto-syncs all affected specs)
|
|
87
|
-
```
|
|
88
|
-
npx --yes @primitive.ai/prim spec map <id> -p "src/auth/**" "src/foo/**" # multiple patterns at once
|
|
89
|
-
npx --yes @primitive.ai/prim spec unmap <id> -p "src/auth/**" # remove one
|
|
90
|
-
npx --yes @primitive.ai/prim spec unmap <id> # clear all manual patterns
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Create or link a context
|
|
94
|
-
```
|
|
95
|
-
npx --yes @primitive.ai/prim context create -s project -n "<name>" --file <path> --project-id <pid> # add --spec to make it a spec
|
|
96
|
-
npx --yes @primitive.ai/prim context create -s global -n "<name>" --text "..." # filed in the global context pane, not linked to a specific project
|
|
97
|
-
npx --yes @primitive.ai/prim context link <ctxId> --project <projectId> # works on any scope
|
|
98
|
-
npx --yes @primitive.ai/prim context unlink <ctxId> --project <projectId> # remove a link
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Update or delete a context
|
|
102
|
-
```
|
|
103
|
-
npx --yes @primitive.ai/prim context update <id> -n "<new name>" # rename
|
|
104
|
-
npx --yes @primitive.ai/prim context update <id> --file <path> # replace body
|
|
105
|
-
npx --yes @primitive.ai/prim context delete <id> # permanent -- confirm with the user first
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Create a project (optionally with a linked spec)
|
|
109
|
-
```
|
|
110
|
-
npx --yes @primitive.ai/prim project create -n "<name>" -d "<desc>"
|
|
111
|
-
npx --yes @primitive.ai/prim project create -n "<name>" --spec <contextId> # value is a context ID
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Link a spec to a branch (and an optional PR)
|
|
115
|
-
|
|
116
|
-
Linking is **automatic** once the pre-commit hook is installed and a spec is bound to your branch — every commit refreshes the link's metadata, including the PR number (detected from `gh pr view` when `gh` is on `PATH`). When a spec was authored for the work on this branch, bind it at the right moment so the hook can take over — do this without waiting for the user to ask. When no spec exists for the work, **do not bind one** — see "Decide whether a spec exists for this branch" below.
|
|
117
|
-
|
|
118
|
-
**At the start of branch work, install the hook** (skip if `.git/hooks/pre-commit` or `.husky/pre-commit` already invokes `prim-pre-commit`):
|
|
119
|
-
```
|
|
120
|
-
npx --yes @primitive.ai/prim hooks install --yes
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**When you create a spec for branch-scoped work, always pass `--branch`** (and `--pr` if a PR already exists):
|
|
124
|
-
```
|
|
125
|
-
br=$(git rev-parse --abbrev-ref HEAD)
|
|
126
|
-
pr=$(gh pr view --json number -q .number 2>/dev/null)
|
|
127
|
-
npx --yes @primitive.ai/prim spec create -s project -n "<name>" --file <path> --branch "$br" ${pr:+--pr "$pr"}
|
|
128
|
-
```
|
|
129
|
-
`--branch` requires a GitHub origin; if `git remote get-url origin` isn't GitHub the link is silently dropped (stderr warning). There is no `prim spec link` subcommand in v1 — to rebind a spec to a different branch, edit it from the spec editor UI.
|
|
68
|
+
Confirmations are author-targeted and rare by design; answering keeps the graph's rationale trustworthy. Don't manufacture rationale — if you don't know why a decision was made, say so.
|
|
130
69
|
|
|
131
|
-
|
|
132
|
-
- the user named a spec ID or title in conversation,
|
|
133
|
-
- a spec was created with `--branch "$br"` (visible via `npx --yes @primitive.ai/prim spec list --json | jq --arg br "$br" '.[] | select(.linkedBranches[]?.branch == $br)'`, where `$br` is the same shell variable set in the recipe above).
|
|
70
|
+
## Presence
|
|
134
71
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
If neither signal applies, **stop and ask the user**:
|
|
138
|
-
|
|
139
|
-
> "I couldn't find a spec associated with this branch. Is there one I should link, or should I draft one from the PR description and our conversation?"
|
|
140
|
-
|
|
141
|
-
Three legitimate answers:
|
|
142
|
-
- **User names an existing spec** → proceed to "When the user has identified a spec for this branch" below.
|
|
143
|
-
- **User declines a spec entirely** → leave the PR unlinked.
|
|
144
|
-
- **User asks you to draft one** →
|
|
145
|
-
- Compose with these sections (drop any that would only restate the obvious): *Goal*, *Requirements / Behavior*, *Technical Approach*, *Key Decisions*, *Out of Scope*. Scope to what the PR actually changed; don't restate the unchanged system. Each fact appears in exactly one section.
|
|
146
|
-
- Voice: plain language; lead with the point; **intent before mechanism** ("users see each other's edits" before "we use WebSocket"); present tense, active voice; one idea per paragraph; cut sentences that don't earn their place.
|
|
147
|
-
- **Key Decisions** is a markdown table — columns *Decision | Rationale | Trade-offs*, one row per non-obvious choice.
|
|
148
|
-
- Title is just the feature name (no `Spec:` prefix); no numbered or parenthetical headers. Match length to PR complexity — a small fix is one screen; a substantial feature warrants the full shape.
|
|
149
|
-
- Do not paste the PR description verbatim — a spec captures *intent*, not a change log. If the rationale behind a non-obvious decision isn't in conversation, ask one or two targeted questions before drafting; do not invent rationale.
|
|
150
|
-
- Show the user the drafted text and wait for go-ahead before running `spec create`.
|
|
151
|
-
- Then create-and-link using the recipe above.
|
|
152
|
-
|
|
153
|
-
**When the user has identified a spec for this branch, check whether it's bound to your branch** before committing:
|
|
154
|
-
```
|
|
155
|
-
npx --yes @primitive.ai/prim context get <specId> --json | jq '.linkedBranches[]?.branch'
|
|
156
|
-
```
|
|
157
|
-
- Your branch appears → done; the hook keeps it fresh.
|
|
158
|
-
- Empty or branch absent → the first commit's hook auto-binds it; no CLI step needed.
|
|
159
|
-
- Bound only to another branch → the hook silently excludes it from your branch's syncs; rebind via the editor UI before committing.
|
|
160
|
-
|
|
161
|
-
**After `gh pr create`**, no CLI step is required: the next commit's hook patches `linkedBranches[].prNumber` via `gh pr view`, and GitHub's webhook to Primitive sets the same field server-side within seconds. Confirm with:
|
|
162
|
-
```
|
|
163
|
-
npx --yes @primitive.ai/prim context get <specId> --json | jq .linkedBranches
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**On every commit, read the `[synced]` line to verify link state** — it piggybacks the suffix:
|
|
167
|
-
- `(auto-linking to <branch>)` — first sync; server is binding now.
|
|
168
|
-
- `(linked to <branch> #<n> <state>)` — link is sticky.
|
|
169
|
-
- `[skip] <id> — not linked to <branch>` — the spec is bound elsewhere; investigate before continuing.
|
|
170
|
-
|
|
171
|
-
### Trigger PR Intent Review or dispatch drift-fix against a linked PR
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
npx --yes @primitive.ai/prim spec review <id> --pr <n> # head SHA defaults to `git rev-parse HEAD`
|
|
175
|
-
npx --yes @primitive.ai/prim spec review <id> --pr <n> --sha <s> # explicit SHA
|
|
176
|
-
npx --yes @primitive.ai/prim spec drift <id> --pr <n> # dispatch the Claude Code drift-fix workflow against the PR
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
The review bot runs server-side, posts a PR comment with findings, and the outcome surfaces on the **next** pre-commit sync's `[synced]` line as ` (reviewed: <n> finding(s) → <prCommentUrl>)` or ` (review failed)` — don't poll the API yourself.
|
|
180
|
-
|
|
181
|
-
`spec drift` requires the `primitive-drift-fix.yml` workflow file checked into the repo and the GitHub App's `actions:write` scope granted on the org. The CLI errors out otherwise with a one-liner naming the likely causes.
|
|
182
|
-
|
|
183
|
-
Neither `spec review` nor `spec drift` accepts `--json` in v1 — they emit a single human-readable line on stdout. Capture it as text or branch on `$?`.
|
|
184
|
-
|
|
185
|
-
### Inspect a task's auto-completion state
|
|
186
|
-
|
|
187
|
-
```
|
|
188
|
-
npx --yes @primitive.ai/prim spec status <taskId>
|
|
189
|
-
```
|
|
72
|
+
With the daemon running (`npx --yes @primitive.ai/prim daemon start`), `npx --yes @primitive.ai/prim daemon status` includes the live online count in its STDOUT JSON (when presence is fresh); Claude Code surfaces it in the statusline as `team: N online`. Your captured decisions are attributed to your agent automatically -- no flag required.
|
|
190
73
|
|
|
191
|
-
|
|
74
|
+
## The git hooks
|
|
192
75
|
|
|
193
|
-
`
|
|
76
|
+
`npx --yes @primitive.ai/prim hooks install` installs two git hooks:
|
|
194
77
|
|
|
195
|
-
### Install the pre-commit hook
|
|
196
78
|
```
|
|
197
79
|
npx --yes @primitive.ai/prim hooks install # auto-detects Husky and prompts
|
|
198
80
|
npx --yes @primitive.ai/prim hooks install --yes # confirm Husky (non-interactive)
|
|
199
81
|
npx --yes @primitive.ai/prim hooks install --target=git-hooks # force .git/hooks (skip Husky detection)
|
|
200
82
|
npx --yes @primitive.ai/prim hooks uninstall
|
|
201
83
|
```
|
|
202
|
-
Under `CI=1` (or with `--non-interactive`), `hooks install` fails fast in a Husky repo unless `--yes` or `--target` is set. The error message names both escapes.
|
|
203
|
-
|
|
204
|
-
**Note:** `hooks uninstall` only removes `.git/hooks/pre-commit`. If the hook was installed into `.husky/pre-commit`, you must remove the prim block from that file manually.
|
|
205
|
-
|
|
206
|
-
## How the pre-commit hook behaves
|
|
207
|
-
|
|
208
|
-
`npx --yes @primitive.ai/prim hooks install` adds a hook that, on every commit:
|
|
209
84
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
3. For each affected spec, sends `git diff --cached` to `/api/cli/contexts/:id/sync-diff`. The backend runs an **LLM over (current spec + diff)** to produce edits, updates the spec text, then applies the new spec to the project.
|
|
213
|
-
4. Prints `[synced] <id> -- <name>` or `[skip] <id> -- <reason>` per affected spec to stdout, and `[error]` lines to stderr.
|
|
85
|
+
- **pre-commit** -- checks staged files against the live decision graph and prints any active decisions that reference them to stderr. It is **warn-only**: failures (auth, network, backend) or matches never block the commit; a successful `git commit` doesn't prove the check ran clean. When the check can't complete it says so ("not verified" / "truncated") rather than implying all-clear.
|
|
86
|
+
- **post-commit** -- records each commit as a capture boundary so the server can classify the surrounding work into decisions. It never blocks and runs in the background.
|
|
214
87
|
|
|
215
|
-
|
|
88
|
+
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`.
|
|
216
89
|
|
|
217
|
-
|
|
218
|
-
- **The hook never blocks the commit.** Failures (auth, network, backend) print `[error]` to stderr but exit 0, so a successful `git commit` doesn't prove the spec changed. Check the hook's `[synced]` / `[error]` / `[skip]` output, or verify with `npx --yes @primitive.ai/prim spec get <id>`.
|
|
219
|
-
- **Diffs over 256 KiB are truncated.** The hook logs `(truncated: X KiB -> Y KiB analyzed)`. The LLM only sees the first 256 KiB of the diff.
|
|
220
|
-
- **The hook is branch-aware.** It sends `repoFullName`, `branch`, `sha`, and `prNumber` (the last detected from `gh pr view` when `gh` is on `PATH`, silently null otherwise). The server filters mappings to specs linked to the current branch *or* unlinked (auto-link candidates); specs bound to other branches are silently excluded from the affected list — they don't surface as `[skip]` lines, they just don't appear. If you push an explicit `sync-diff` for an other-branch spec via the API, the hook logs `[skip] <id> — <name> — not linked to <branch>` and continues.
|
|
221
|
-
- **Link state and review results piggyback on the synced line.** `[synced]` lines carry ` (linked to <branch> #<pr> <state>)` or ` (auto-linking to <branch>)`, and once a PR Intent Review completes they grow ` (reviewed: <n> finding(s) → <prCommentUrl>)` or ` (review failed)`. PR `<state>` (`open` / `closed` / `merged`) tracks GitHub webhook deliveries — give it a few seconds to settle after a state change.
|
|
222
|
-
- **To suppress the hook for one commit** (e.g., when intentionally desyncing code from spec, or when committing unrelated changes), use `git commit --no-verify`.
|
|
90
|
+
These git hooks are separate from the **session hooks** (`claude install` / `codex install`) that drive in-session capture and the conflict gate.
|
|
223
91
|
|
|
224
92
|
## Output formats
|
|
225
93
|
|
|
226
|
-
|
|
94
|
+
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.
|
|
227
95
|
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
|
|
232
|
-
Without `--json`, mutating commands (`context create/update/delete/link/unlink`, `spec create/update/sync/map/unmap/auto-map`, `project create`) emit the bare resource `_id` to **stdout** (one line, no prefix) and human-readable diagnostics to **stderr**. So this also works as a one-liner without `jq`:
|
|
96
|
+
- **STDOUT is machine-readable** — JSON (one document per invocation). `decisions` reads project lean shapes, not raw rows.
|
|
97
|
+
- **STDERR is human-readable** — a verdict-first line, plus the gate/verdict-footer/presence notes.
|
|
98
|
+
- **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.
|
|
233
99
|
|
|
234
|
-
|
|
100
|
+
Examples:
|
|
235
101
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
| `context list`, `spec list` (non-empty) | stdout: rows (first token = `_id`); stderr: `N context(s)` / `N spec(s)` summary | stdout: JSON array |
|
|
240
|
-
| `context list`, `spec list` (empty) | stdout: (empty); stderr: `No contexts found.` / `No spec documents found.` | stdout: `[]` |
|
|
241
|
-
| `spec list --project-id <pid>` | stdout: key:value block (or stdout empty + stderr `No spec document found for this project.` if none) | stdout: single object or `null` |
|
|
242
|
-
| `context get <id>` | stdout: pretty-printed JSON (always JSON; `--json` accepted for symmetry) | stdout: pretty-printed JSON |
|
|
243
|
-
| `spec get <id>` | stdout: human-readable key:value block (`ID:` line first) | stdout: JSON object |
|
|
244
|
-
| `spec get <id> --text-only` | stdout: raw spec markdown, nothing else | stdout: JSON object (`--json` wins over `--text-only`) |
|
|
245
|
-
| `auth status` | stdout: human readout; **exit code is the authoritative signal** (0 = authed) | stdout: JSON; exit code unchanged |
|
|
102
|
+
- `npx --yes @primitive.ai/prim auth status --json | jq -r .authenticated` — boolean; the exit code remains the authoritative signal
|
|
103
|
+
- `npx --yes @primitive.ai/prim decisions recent | jq -r '.decisions[].shortId'` — list recent decision short ids (STDOUT is already JSON)
|
|
104
|
+
- `npx --yes @primitive.ai/prim decisions show <id> | jq .` — full decision detail
|
|
246
105
|
|
|
247
106
|
## Pitfalls
|
|
248
107
|
|
|
249
|
-
-
|
|
250
|
-
-
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
- **Scope is set at creation.** To change it, delete and recreate the context.
|
|
255
|
-
- **The hook silently excludes specs bound to other branches.** If you don't see a spec you expected to sync, check its `linkedBranches[]` via `npx --yes @primitive.ai/prim context get <id>` — it may be bound to a different branch. To re-bind, use the spec editor (no CLI subcommand in v1).
|
|
108
|
+
- **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.
|
|
109
|
+
- **A `deny` means a real prior decision conflicts.** Reconcile only when you genuinely intend to override it; otherwise pick an approach that respects it.
|
|
110
|
+
- **Reconcile bypasses are single-use and short-lived.** One bypass clears your *next* edit to the governed file; it is not a standing override.
|
|
111
|
+
- **Capture is automatic, never manual.** If decisions aren't showing up, check that the session hooks are installed (`claude status` / `codex status`) and the daemon is running — don't try to inject moves by hand.
|
|
112
|
+
- **Don't fabricate rationale on a confirmation.** If you don't know why a decision was made, say so rather than guessing.
|
|
256
113
|
|
|
257
114
|
## After each task
|
|
258
115
|
|
|
259
|
-
|
|
116
|
+
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.
|
|
@@ -167,9 +167,7 @@ async function request(method, path, body, options) {
|
|
|
167
167
|
function getClient() {
|
|
168
168
|
return {
|
|
169
169
|
get: (path, options) => request("GET", path, void 0, options),
|
|
170
|
-
post: (path, body, options) => request("POST", path, body, options)
|
|
171
|
-
patch: (path, body, options) => request("PATCH", path, body, options),
|
|
172
|
-
delete: (path, options) => request("DELETE", path, void 0, options)
|
|
170
|
+
post: (path, body, options) => request("POST", path, body, options)
|
|
173
171
|
};
|
|
174
172
|
}
|
|
175
173
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-26VA3ADF.js";
|
|
4
4
|
import {
|
|
5
5
|
daemonRequest
|
|
6
6
|
} from "./chunk-UTKQTZHL.js";
|
|
@@ -115,37 +115,8 @@ function formatDecisionsWarning(result) {
|
|
|
115
115
|
return lines.join("\n");
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
// src/utils/git.ts
|
|
119
|
-
import { execSync } from "child_process";
|
|
120
|
-
function safeExec(cmd) {
|
|
121
|
-
try {
|
|
122
|
-
return execSync(cmd, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function parseRepoFullName(remoteUrl) {
|
|
128
|
-
const match = remoteUrl.match(/(?:github\.com[:/])([^/]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
129
|
-
return match ? `${match[1]}/${match[2]}` : null;
|
|
130
|
-
}
|
|
131
|
-
function getGitContext() {
|
|
132
|
-
const branchRaw = safeExec("git rev-parse --abbrev-ref HEAD");
|
|
133
|
-
const branch = branchRaw && branchRaw !== "HEAD" ? branchRaw : null;
|
|
134
|
-
const sha = safeExec("git rev-parse HEAD");
|
|
135
|
-
const remoteUrl = safeExec("git remote get-url origin");
|
|
136
|
-
const repoFullName = remoteUrl ? parseRepoFullName(remoteUrl) : null;
|
|
137
|
-
let prNumber = null;
|
|
138
|
-
if (safeExec("command -v gh")) {
|
|
139
|
-
const raw = safeExec("gh pr view --json number -q .number");
|
|
140
|
-
const n = raw ? Number.parseInt(raw, 10) : Number.NaN;
|
|
141
|
-
if (Number.isFinite(n)) prNumber = n;
|
|
142
|
-
}
|
|
143
|
-
return { branch, sha, repoFullName, prNumber };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
118
|
export {
|
|
147
119
|
daemonOrDirectGet,
|
|
148
120
|
checkAffectedDecisions,
|
|
149
|
-
formatDecisionsWarning
|
|
150
|
-
getGitContext
|
|
121
|
+
formatDecisionsWarning
|
|
151
122
|
};
|
package/dist/daemon/server.js
CHANGED