@primitive.ai/prim 0.1.0-alpha.31 → 0.1.0-alpha.33
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 +8 -3
- package/SKILL.md +6 -6
- package/dist/{chunk-7GHOFNJ2.js → chunk-S2O4P4A3.js} +4 -4
- package/dist/chunk-TCDUH7AN.js +35 -0
- package/dist/hooks/post-commit.js +1 -1
- package/dist/hooks/post-tool-use.js +18 -7
- package/dist/hooks/pre-tool-use.js +64 -17
- package/dist/hooks/prim-hook.js +7 -5
- package/dist/hooks/session-end.js +9 -1
- package/dist/hooks/session-start.js +10 -5
- package/dist/index.js +482 -93
- package/package.json +3 -2
- package/dist/chunk-7YRBACIE.js +0 -9
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ One command does the whole install — auth, session hooks, daemon, git hooks,
|
|
|
28
28
|
skill, and the welcome:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
prim setup # add --agent codex
|
|
31
|
+
prim setup # add --agent codex or --agent hermes, --no-daemon to skip the daemon
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Or run the steps individually:
|
|
@@ -38,7 +38,7 @@ Or run the steps individually:
|
|
|
38
38
|
prim auth login
|
|
39
39
|
|
|
40
40
|
# 2. Wire the session hooks (decision capture + conflict gate + presence)
|
|
41
|
-
prim claude install # or: prim codex install
|
|
41
|
+
prim claude install # or: prim codex install / prim hermes install
|
|
42
42
|
|
|
43
43
|
# 3. Start the companion daemon (latency + team presence)
|
|
44
44
|
prim daemon start
|
|
@@ -74,6 +74,7 @@ and the manual fallback — live in [`setup.md`](./setup.md).
|
|
|
74
74
|
```bash
|
|
75
75
|
prim setup # Run the whole install in one shot
|
|
76
76
|
prim setup --agent codex # Same, for OpenAI Codex
|
|
77
|
+
prim setup --agent hermes # Same, for Hermes Agent (global-only config)
|
|
77
78
|
prim setup --no-daemon # Skip the companion daemon
|
|
78
79
|
```
|
|
79
80
|
|
|
@@ -99,12 +100,16 @@ self-resolves the CLI at run time (PATH, then a local install, then
|
|
|
99
100
|
|
|
100
101
|
Installs into the current project by default — the repo's `.claude/settings.json`
|
|
101
102
|
/ `.codex/hooks.json`, resolved from the git root (so any subdirectory works);
|
|
102
|
-
pass `--scope user` to install machine-wide.
|
|
103
|
+
pass `--scope user` to install machine-wide. Hermes is the exception: it reads
|
|
104
|
+
shell hooks only from the global `~/.hermes/config.yaml`, so `prim hermes install`
|
|
105
|
+
is always user-scoped — and prim merges in place, leaving the rest of that file
|
|
106
|
+
(providers, models, your own hooks) untouched.
|
|
103
107
|
|
|
104
108
|
```bash
|
|
105
109
|
prim claude install # Install Claude Code hooks (project scope; uninstall / status)
|
|
106
110
|
prim claude install --scope user # Install machine-wide instead
|
|
107
111
|
prim codex install # Install OpenAI Codex hooks (project scope)
|
|
112
|
+
prim hermes install # Install Hermes Agent hooks (global ~/.hermes/config.yaml)
|
|
108
113
|
```
|
|
109
114
|
|
|
110
115
|
### Daemon
|
package/SKILL.md
CHANGED
|
@@ -11,7 +11,7 @@ description: Use the prim CLI for Primitive's decision graph — passive decisio
|
|
|
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 (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.
|
|
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)
|
|
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.
|
|
15
15
|
|
|
16
16
|
## Auth
|
|
17
17
|
|
|
@@ -33,10 +33,10 @@ The CLI auto-refreshes a still-valid session from the stored refresh token (proa
|
|
|
33
33
|
|
|
34
34
|
## Heed the conflict gate
|
|
35
35
|
|
|
36
|
-
Before an edit (Claude Code: Edit/Write/MultiEdit; Codex: apply_patch) a PreToolUse hook scores the target file against the graph:
|
|
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:
|
|
37
37
|
|
|
38
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.
|
|
39
|
-
- **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.
|
|
39
|
+
- **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
40
|
- **"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
41
|
|
|
42
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,7 +44,7 @@ The gate fail-opens on its *own* infrastructure errors (no daemon, network blip,
|
|
|
44
44
|
## Read the graph before large or load-bearing edits
|
|
45
45
|
|
|
46
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.
|
|
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.
|
|
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` / `Your Hermes`); `--limit <n>` and `--since <dur>` narrow it.
|
|
48
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.
|
|
49
49
|
|
|
50
50
|
## Reconcile and the verdict footer
|
|
@@ -116,7 +116,7 @@ npx --yes @primitive.ai/prim hooks uninstall
|
|
|
116
116
|
|
|
117
117
|
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
118
|
|
|
119
|
-
These git hooks are separate from the **session hooks** (`claude install` / `codex install`) that drive in-session capture and the conflict gate.
|
|
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.
|
|
120
120
|
|
|
121
121
|
## Output formats
|
|
122
122
|
|
|
@@ -137,7 +137,7 @@ Examples:
|
|
|
137
137
|
- **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.
|
|
138
138
|
- **A `deny` means a real prior decision conflicts.** Reconcile only when you genuinely intend to override it; otherwise pick an approach that respects it.
|
|
139
139
|
- **Reconcile bypasses are single-use and short-lived.** One bypass clears your *next* edit to the governed file; it is not a standing override.
|
|
140
|
-
- **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`) 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.)
|
|
140
|
+
- **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.)
|
|
141
141
|
- **Don't fabricate rationale on a confirmation.** If you don't know why a decision was made, say so rather than guessing.
|
|
142
142
|
|
|
143
143
|
## After each task
|
|
@@ -19,10 +19,10 @@ function toMove(parsed, cliVersion, agent = "claude_code") {
|
|
|
19
19
|
osPlatform: platform()
|
|
20
20
|
},
|
|
21
21
|
envelopeVersion: ENVELOPE_VERSION,
|
|
22
|
-
// Stamp the producer
|
|
23
|
-
// backend defaults an absent value to "claude_code"),
|
|
24
|
-
// Claude wire shape byte-identical.
|
|
25
|
-
...agent === "
|
|
22
|
+
// Stamp the producer for non-Claude agents (codex, hermes); Claude Code
|
|
23
|
+
// moves omit it (the backend defaults an absent value to "claude_code"),
|
|
24
|
+
// keeping the Claude wire shape byte-identical.
|
|
25
|
+
...agent === "claude_code" ? {} : { producer: agent }
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
function toCommitMove(commit, cliVersion, cwd) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// src/hooks/agent.ts
|
|
2
|
+
function parseAgent(argv) {
|
|
3
|
+
const i = argv.indexOf("--agent");
|
|
4
|
+
const value = i !== -1 ? argv[i + 1] : void 0;
|
|
5
|
+
if (value === "codex" || value === "hermes") {
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
return "claude_code";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/hooks/normalize.ts
|
|
12
|
+
var HERMES_EVENT_MAP = {
|
|
13
|
+
on_session_start: "SessionStart",
|
|
14
|
+
on_session_end: "SessionEnd",
|
|
15
|
+
pre_llm_call: "UserPromptSubmit",
|
|
16
|
+
post_llm_call: "Stop",
|
|
17
|
+
pre_tool_call: "PreToolUse",
|
|
18
|
+
post_tool_call: "PostToolUse",
|
|
19
|
+
subagent_stop: "SubagentStop"
|
|
20
|
+
};
|
|
21
|
+
function normalizeEnvelope(parsed, agent) {
|
|
22
|
+
if (agent !== "hermes") {
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
const raw = parsed.hook_event_name;
|
|
26
|
+
if (typeof raw === "string" && raw in HERMES_EVENT_MAP) {
|
|
27
|
+
return { ...parsed, hook_event_name: HERMES_EVENT_MAP[raw] };
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
parseAgent,
|
|
34
|
+
normalizeEnvelope
|
|
35
|
+
};
|
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
} from "../chunk-6LAQVM26.js";
|
|
9
9
|
import {
|
|
10
10
|
toMove
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import {
|
|
13
|
-
parseAgent
|
|
14
|
-
} from "../chunk-7YRBACIE.js";
|
|
11
|
+
} from "../chunk-S2O4P4A3.js";
|
|
15
12
|
import {
|
|
16
13
|
getClient
|
|
17
14
|
} from "../chunk-26VA3ADF.js";
|
|
15
|
+
import {
|
|
16
|
+
normalizeEnvelope,
|
|
17
|
+
parseAgent
|
|
18
|
+
} from "../chunk-TCDUH7AN.js";
|
|
18
19
|
|
|
19
20
|
// src/hooks/post-tool-use.ts
|
|
20
21
|
import { readFileSync } from "fs";
|
|
@@ -42,6 +43,16 @@ var STDIN_TIMEOUT_MS = 1e3;
|
|
|
42
43
|
var INGEST_TIMEOUT_MS = 4e3;
|
|
43
44
|
var EDITING_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
|
|
44
45
|
var CODEX_EDITING_TOOLS = /* @__PURE__ */ new Set(["apply_patch"]);
|
|
46
|
+
var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
|
|
47
|
+
function editingToolsFor(agent) {
|
|
48
|
+
if (agent === "codex") {
|
|
49
|
+
return CODEX_EDITING_TOOLS;
|
|
50
|
+
}
|
|
51
|
+
if (agent === "hermes") {
|
|
52
|
+
return HERMES_EDITING_TOOLS;
|
|
53
|
+
}
|
|
54
|
+
return EDITING_TOOLS;
|
|
55
|
+
}
|
|
45
56
|
var here = dirname(fileURLToPath(import.meta.url));
|
|
46
57
|
function resolveCliVersion() {
|
|
47
58
|
try {
|
|
@@ -86,6 +97,7 @@ async function ingestMove(move) {
|
|
|
86
97
|
);
|
|
87
98
|
}
|
|
88
99
|
async function main() {
|
|
100
|
+
const agent = parseAgent(process.argv);
|
|
89
101
|
let raw;
|
|
90
102
|
try {
|
|
91
103
|
raw = await readStdin();
|
|
@@ -95,7 +107,7 @@ async function main() {
|
|
|
95
107
|
}
|
|
96
108
|
let parsed;
|
|
97
109
|
try {
|
|
98
|
-
parsed = JSON.parse(raw);
|
|
110
|
+
parsed = normalizeEnvelope(JSON.parse(raw), agent);
|
|
99
111
|
} catch {
|
|
100
112
|
emit();
|
|
101
113
|
return;
|
|
@@ -106,8 +118,7 @@ async function main() {
|
|
|
106
118
|
return;
|
|
107
119
|
}
|
|
108
120
|
const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
|
|
109
|
-
const
|
|
110
|
-
const editingTools = agent === "codex" ? CODEX_EDITING_TOOLS : EDITING_TOOLS;
|
|
121
|
+
const editingTools = editingToolsFor(agent);
|
|
111
122
|
if (!editingTools.has(toolName)) {
|
|
112
123
|
emit();
|
|
113
124
|
return;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
parseAgent
|
|
4
|
-
} from "../chunk-7YRBACIE.js";
|
|
5
2
|
import {
|
|
6
3
|
getClient,
|
|
7
4
|
getSiteUrl
|
|
@@ -9,6 +6,10 @@ import {
|
|
|
9
6
|
import {
|
|
10
7
|
daemonRequest
|
|
11
8
|
} from "../chunk-UTKQTZHL.js";
|
|
9
|
+
import {
|
|
10
|
+
normalizeEnvelope,
|
|
11
|
+
parseAgent
|
|
12
|
+
} from "../chunk-TCDUH7AN.js";
|
|
12
13
|
|
|
13
14
|
// src/hooks/pre-tool-use-scoring.ts
|
|
14
15
|
import { isAbsolute, relative, sep } from "path";
|
|
@@ -47,7 +48,7 @@ function unverifiedNote(results) {
|
|
|
47
48
|
}
|
|
48
49
|
return causes.map((c) => `[primitive] ${c}`).join("\n");
|
|
49
50
|
}
|
|
50
|
-
function buildHookOutput(aggregate, results,
|
|
51
|
+
function buildHookOutput(aggregate, results, agent2 = "claude_code") {
|
|
51
52
|
if (aggregate === "deny") {
|
|
52
53
|
const reason = results.filter((r) => r.verdict === "deny").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n") || "[primitive] conflict detected (no detail available)";
|
|
53
54
|
return {
|
|
@@ -58,7 +59,7 @@ function buildHookOutput(aggregate, results, agent = "claude_code") {
|
|
|
58
59
|
}
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
|
-
if (
|
|
62
|
+
if (agent2 === "codex" && aggregate === "ask") {
|
|
62
63
|
const reason = results.filter((r) => r.verdict === "ask" || r.verdict === "deny").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n");
|
|
63
64
|
const context = results.map((r) => r.additionalContext).filter((s) => s.length > 0).join("\n");
|
|
64
65
|
const merged = [reason, context].filter((s) => s.length > 0).join("\n\n");
|
|
@@ -113,8 +114,21 @@ function failOpenOutput() {
|
|
|
113
114
|
}
|
|
114
115
|
};
|
|
115
116
|
}
|
|
117
|
+
function buildHermesOutput(aggregate, results) {
|
|
118
|
+
if (aggregate !== "deny" && aggregate !== "ask") {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
const reason = results.filter((r) => r.verdict === "deny" || r.verdict === "ask").map((r) => r.reason).filter((s) => s.length > 0).join("\n\n");
|
|
122
|
+
const directive = results.map((r) => r.additionalContext).filter((s) => s.length > 0).join("\n");
|
|
123
|
+
const message = [reason, directive].filter((s) => s.length > 0).join("\n\n") || "[primitive] conflict detected (no detail available)";
|
|
124
|
+
return { action: "block", message };
|
|
125
|
+
}
|
|
126
|
+
function failOpenHermes() {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
116
129
|
var SUPPORTED_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
|
|
117
130
|
var APPLY_PATCH_FILE_RE = /^\*\*\* (?:Update|Add|Delete) File: (?<path>.+)$/;
|
|
131
|
+
var MOVE_FILE_RE = /^\*\*\* Move File:\s*(?<src>.+?)\s*->\s*(?<dst>.+?)\s*$/;
|
|
118
132
|
var LINE_SPLIT_RE = /\r?\n/;
|
|
119
133
|
function parseApplyPatchPaths(command) {
|
|
120
134
|
const paths = /* @__PURE__ */ new Set();
|
|
@@ -122,6 +136,14 @@ function parseApplyPatchPaths(command) {
|
|
|
122
136
|
const path = APPLY_PATCH_FILE_RE.exec(line)?.groups?.path?.trim();
|
|
123
137
|
if (path) {
|
|
124
138
|
paths.add(path);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const move = MOVE_FILE_RE.exec(line)?.groups;
|
|
142
|
+
if (move?.src) {
|
|
143
|
+
paths.add(move.src.trim());
|
|
144
|
+
if (move.dst) {
|
|
145
|
+
paths.add(move.dst.trim());
|
|
146
|
+
}
|
|
125
147
|
}
|
|
126
148
|
}
|
|
127
149
|
return Array.from(paths);
|
|
@@ -136,10 +158,27 @@ function extractCodexFilePaths(toolName, toolInput) {
|
|
|
136
158
|
const command = toolInput.command;
|
|
137
159
|
return typeof command === "string" ? parseApplyPatchPaths(command) : [];
|
|
138
160
|
}
|
|
139
|
-
|
|
140
|
-
|
|
161
|
+
var HERMES_EDITING_TOOLS = /* @__PURE__ */ new Set(["write_file", "patch"]);
|
|
162
|
+
function extractHermesFilePaths(toolName, toolInput) {
|
|
163
|
+
if (!HERMES_EDITING_TOOLS.has(toolName)) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
if (!toolInput || typeof toolInput !== "object") {
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
const input = toolInput;
|
|
170
|
+
if (toolName === "patch" && input.mode === "patch") {
|
|
171
|
+
return typeof input.patch === "string" ? parseApplyPatchPaths(input.patch) : [];
|
|
172
|
+
}
|
|
173
|
+
return typeof input.path === "string" && input.path.length > 0 ? [input.path] : [];
|
|
174
|
+
}
|
|
175
|
+
function extractFilePaths(toolName, toolInput, agent2 = "claude_code") {
|
|
176
|
+
if (agent2 === "codex") {
|
|
141
177
|
return extractCodexFilePaths(toolName, toolInput);
|
|
142
178
|
}
|
|
179
|
+
if (agent2 === "hermes") {
|
|
180
|
+
return extractHermesFilePaths(toolName, toolInput);
|
|
181
|
+
}
|
|
143
182
|
if (!SUPPORTED_TOOLS.has(toolName)) {
|
|
144
183
|
return [];
|
|
145
184
|
}
|
|
@@ -193,10 +232,14 @@ async function readStdin() {
|
|
|
193
232
|
});
|
|
194
233
|
});
|
|
195
234
|
}
|
|
235
|
+
var agent = parseAgent(process.argv);
|
|
196
236
|
function emit(output) {
|
|
197
237
|
process.stdout.write(`${JSON.stringify(output)}
|
|
198
238
|
`);
|
|
199
239
|
}
|
|
240
|
+
function failOpen() {
|
|
241
|
+
return agent === "hermes" ? failOpenHermes() : failOpenOutput();
|
|
242
|
+
}
|
|
200
243
|
async function checkOneFile(file) {
|
|
201
244
|
const fromDaemon = await daemonRequest(
|
|
202
245
|
"conflict_check",
|
|
@@ -220,47 +263,51 @@ async function main() {
|
|
|
220
263
|
try {
|
|
221
264
|
raw = await readStdin();
|
|
222
265
|
} catch {
|
|
223
|
-
emit(
|
|
266
|
+
emit(failOpen());
|
|
224
267
|
return;
|
|
225
268
|
}
|
|
226
269
|
let envelope;
|
|
227
270
|
try {
|
|
228
|
-
envelope =
|
|
271
|
+
envelope = normalizeEnvelope(
|
|
272
|
+
JSON.parse(raw),
|
|
273
|
+
agent
|
|
274
|
+
);
|
|
229
275
|
} catch {
|
|
230
|
-
emit(
|
|
276
|
+
emit(failOpen());
|
|
231
277
|
return;
|
|
232
278
|
}
|
|
233
279
|
if (envelope.hook_event_name !== "PreToolUse") {
|
|
234
|
-
emit(
|
|
280
|
+
emit(failOpen());
|
|
235
281
|
return;
|
|
236
282
|
}
|
|
237
283
|
const env = process.env;
|
|
238
284
|
const mode = readHookMode(env);
|
|
239
285
|
if (mode === "off") {
|
|
240
|
-
emit(
|
|
286
|
+
emit(failOpen());
|
|
241
287
|
return;
|
|
242
288
|
}
|
|
243
289
|
const toolName = typeof envelope.tool_name === "string" ? envelope.tool_name : "";
|
|
244
290
|
const cwd = typeof envelope.cwd === "string" && envelope.cwd.length > 0 ? envelope.cwd : process.cwd();
|
|
245
|
-
const agent = parseAgent(process.argv);
|
|
246
291
|
const files = extractFilePaths(toolName, envelope.tool_input, agent).map(
|
|
247
292
|
(f) => toRepoRelative(f, cwd)
|
|
248
293
|
);
|
|
249
294
|
if (files.length === 0) {
|
|
250
|
-
emit(
|
|
295
|
+
emit(failOpen());
|
|
251
296
|
return;
|
|
252
297
|
}
|
|
253
298
|
let results;
|
|
254
299
|
try {
|
|
255
300
|
results = await Promise.all(files.map((f) => checkOneFile(f)));
|
|
256
301
|
} catch {
|
|
257
|
-
emit(
|
|
302
|
+
emit(failOpen());
|
|
258
303
|
return;
|
|
259
304
|
}
|
|
260
305
|
const rawAggregate = aggregateCheckResults(results);
|
|
261
306
|
const aggregate = demoteForMode(rawAggregate, mode);
|
|
262
|
-
emit(
|
|
307
|
+
emit(
|
|
308
|
+
agent === "hermes" ? buildHermesOutput(aggregate, results) : buildHookOutput(aggregate, results, agent)
|
|
309
|
+
);
|
|
263
310
|
}
|
|
264
311
|
main().catch(() => {
|
|
265
|
-
emit(
|
|
312
|
+
emit(failOpen());
|
|
266
313
|
});
|
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
shouldFlushAfter,
|
|
11
11
|
toMove
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-S2O4P4A3.js";
|
|
13
|
+
import "../chunk-26VA3ADF.js";
|
|
13
14
|
import {
|
|
15
|
+
normalizeEnvelope,
|
|
14
16
|
parseAgent
|
|
15
|
-
} from "../chunk-
|
|
16
|
-
import "../chunk-26VA3ADF.js";
|
|
17
|
+
} from "../chunk-TCDUH7AN.js";
|
|
17
18
|
|
|
18
19
|
// src/hooks/prim-hook.ts
|
|
19
20
|
import { spawn } from "child_process";
|
|
@@ -37,10 +38,11 @@ function spawnBackgroundFlush() {
|
|
|
37
38
|
}).unref();
|
|
38
39
|
}
|
|
39
40
|
try {
|
|
41
|
+
const agent = parseAgent(process.argv);
|
|
40
42
|
const raw = readFileSync(0, "utf-8");
|
|
41
|
-
const parsed = JSON.parse(raw);
|
|
43
|
+
const parsed = normalizeEnvelope(JSON.parse(raw), agent);
|
|
42
44
|
const cwd = parsed.cwd ?? process.cwd();
|
|
43
|
-
const base = toMove(parsed, resolveCliVersion(),
|
|
45
|
+
const base = toMove(parsed, resolveCliVersion(), agent);
|
|
44
46
|
const move = { ...base, payload: scrubFromCwd(parsed, cwd) };
|
|
45
47
|
const { orgId } = resolveOrg({ sessionId: move.sessionId, cwd: move.env.cwd });
|
|
46
48
|
appendMove(move, orgId);
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
daemonRequest
|
|
4
4
|
} from "../chunk-UTKQTZHL.js";
|
|
5
|
+
import {
|
|
6
|
+
normalizeEnvelope,
|
|
7
|
+
parseAgent
|
|
8
|
+
} from "../chunk-TCDUH7AN.js";
|
|
5
9
|
|
|
6
10
|
// src/hooks/session-end.ts
|
|
7
11
|
var STDIN_TIMEOUT_MS = 1e3;
|
|
@@ -27,6 +31,7 @@ function emit() {
|
|
|
27
31
|
process.stdout.write("{}\n");
|
|
28
32
|
}
|
|
29
33
|
async function main() {
|
|
34
|
+
const agent = parseAgent(process.argv);
|
|
30
35
|
let raw;
|
|
31
36
|
try {
|
|
32
37
|
raw = await readStdin();
|
|
@@ -36,7 +41,10 @@ async function main() {
|
|
|
36
41
|
}
|
|
37
42
|
let envelope;
|
|
38
43
|
try {
|
|
39
|
-
envelope =
|
|
44
|
+
envelope = normalizeEnvelope(
|
|
45
|
+
JSON.parse(raw),
|
|
46
|
+
agent
|
|
47
|
+
);
|
|
40
48
|
} catch {
|
|
41
49
|
emit();
|
|
42
50
|
return;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
parseAgent
|
|
4
|
-
} from "../chunk-7YRBACIE.js";
|
|
5
2
|
import {
|
|
6
3
|
getSiteUrl
|
|
7
4
|
} from "../chunk-26VA3ADF.js";
|
|
8
5
|
import {
|
|
9
6
|
daemonRequest
|
|
10
7
|
} from "../chunk-UTKQTZHL.js";
|
|
8
|
+
import {
|
|
9
|
+
normalizeEnvelope,
|
|
10
|
+
parseAgent
|
|
11
|
+
} from "../chunk-TCDUH7AN.js";
|
|
11
12
|
|
|
12
13
|
// src/hooks/session-start.ts
|
|
13
14
|
var STDIN_TIMEOUT_MS = 1e3;
|
|
@@ -44,6 +45,7 @@ function emit(additionalContext) {
|
|
|
44
45
|
`);
|
|
45
46
|
}
|
|
46
47
|
async function main() {
|
|
48
|
+
const agent = parseAgent(process.argv);
|
|
47
49
|
let raw;
|
|
48
50
|
try {
|
|
49
51
|
raw = await readStdin();
|
|
@@ -53,7 +55,10 @@ async function main() {
|
|
|
53
55
|
}
|
|
54
56
|
let envelope;
|
|
55
57
|
try {
|
|
56
|
-
envelope =
|
|
58
|
+
envelope = normalizeEnvelope(
|
|
59
|
+
JSON.parse(raw),
|
|
60
|
+
agent
|
|
61
|
+
);
|
|
57
62
|
} catch {
|
|
58
63
|
emit();
|
|
59
64
|
return;
|
|
@@ -71,7 +76,7 @@ async function main() {
|
|
|
71
76
|
{ sessionId: envelope.session_id },
|
|
72
77
|
{ timeoutMs: DAEMON_TIMEOUT_MS }
|
|
73
78
|
);
|
|
74
|
-
if (
|
|
79
|
+
if (agent === "codex") {
|
|
75
80
|
const snapshot = await daemonRequest(
|
|
76
81
|
"status_snapshot",
|
|
77
82
|
// callerEnv: a cross-env daemon withholds onlineCount, so a prod Codex
|