greprag 5.74.21 → 5.75.0

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.
@@ -146,6 +146,9 @@ function readVersion() {
146
146
  return 'unknown';
147
147
  }
148
148
  }
149
+ function grokHooksPath() {
150
+ return path.join(home(), '.grok', 'hooks', 'greprag.json');
151
+ }
149
152
  function platformFromArgs(args) {
150
153
  if (args.includes('--claude'))
151
154
  return 'claude';
@@ -153,6 +156,8 @@ function platformFromArgs(args) {
153
156
  return 'codex';
154
157
  if (args.includes('--opencode'))
155
158
  return 'opencode';
159
+ if (args.includes('--grok'))
160
+ return 'grok';
156
161
  if (args.includes('--all-platforms'))
157
162
  return 'all';
158
163
  return 'all';
@@ -167,6 +172,8 @@ function buildStatus(cwd, platform = 'all') {
167
172
  const identityCache = (0, identity_1.readCache)(identityCachePath);
168
173
  const codexSkillPath = path.join(home(), '.codex', 'skills', 'greprag');
169
174
  const claudeSkillPath = path.join(home(), '.claude', 'skills', 'greprag');
175
+ const grokSkillPath = path.join(home(), '.grok', 'skills', 'greprag');
176
+ const grokHooks = readJson(grokHooksPath(), {});
170
177
  const opencodePluginPath = path.join(home(), '.config', 'opencode', 'plugins', 'greprag-memory.js');
171
178
  const claudeHooks = {
172
179
  session_start_recap: hasHook(settings.hooks?.SessionStart, 'recap'),
@@ -187,6 +194,15 @@ function buildStatus(cwd, platform = 'all') {
187
194
  post_compact_session_id: hasCodexHook(codexHooks.hooks?.PostCompact, 'session-id'),
188
195
  };
189
196
  const opencodePluginInstalled = fs.existsSync(opencodePluginPath);
197
+ const grokHookStatus = {
198
+ session_start_recap: hasCodexHook(grokHooks.hooks?.SessionStart, 'recap'),
199
+ session_id: hasCodexHook(grokHooks.hooks?.SessionStart, 'session-id'),
200
+ session_start_drain: hasCodexHook(grokHooks.hooks?.SessionStart, 'drain'),
201
+ user_prompt_submit: hasCodexHook(grokHooks.hooks?.UserPromptSubmit, 'notify'),
202
+ crush_wrap: hasCodexHook(grokHooks.hooks?.PreToolUse, 'crush-wrap'),
203
+ stop_store: hasCodexHook(grokHooks.hooks?.Stop, 'store'),
204
+ stop_drain: hasCodexHook(grokHooks.hooks?.Stop, 'drain'),
205
+ };
190
206
  return {
191
207
  installed: true,
192
208
  version: readVersion(),
@@ -212,6 +228,8 @@ function buildStatus(cwd, platform = 'all') {
212
228
  codex_pre_tool_use_safety: codexHookStatus.pre_tool_use_safety,
213
229
  codex_pre_tool_use_chip_guard: codexHookStatus.pre_tool_use_chip_guard,
214
230
  codex_stop_store: codexHookStatus.stop_store,
231
+ grok_session_start_recap: grokHookStatus.session_start_recap,
232
+ grok_stop_store: grokHookStatus.stop_store,
215
233
  },
216
234
  platforms: {
217
235
  claude: {
@@ -241,6 +259,16 @@ function buildStatus(cwd, platform = 'all') {
241
259
  plugin_installed: opencodePluginInstalled,
242
260
  note: 'OpenCode loads the GrepRAG plugin from its global plugin directory.',
243
261
  },
262
+ grok: {
263
+ configured: !!api.key && grokHookStatus.session_start_recap && grokHookStatus.stop_store
264
+ && fs.existsSync(grokSkillPath),
265
+ config_path: grokHooksPath(),
266
+ config_present: fs.existsSync(grokHooksPath()),
267
+ skill_path: grokSkillPath,
268
+ skill_installed: fs.existsSync(grokSkillPath),
269
+ hooks: grokHookStatus,
270
+ note: 'Grok recap is a sidecar + ~/.grok/rules (SessionStart stdout is ignored). Idle inbox: Grok monitor + quiet watch. Stop drain injects unread mail.',
271
+ },
244
272
  },
245
273
  project: {
246
274
  cwd: path.resolve(cwd),
@@ -293,7 +321,7 @@ function renderPlatform(name, p) {
293
321
  }
294
322
  function renderHuman(s, docMirror) {
295
323
  const yes = (b) => b ? 'yes' : 'no';
296
- const platformNames = s.platform === 'all' ? ['claude', 'codex', 'opencode'] : [s.platform];
324
+ const platformNames = s.platform === 'all' ? ['claude', 'codex', 'opencode', 'grok'] : [s.platform];
297
325
  return [
298
326
  `greprag ${s.version}`,
299
327
  `Shared env: ${s.shared_env_path}`,
@@ -79,13 +79,14 @@ exports.gcDeadSessions = gcDeadSessions;
79
79
  const fs = __importStar(require("fs"));
80
80
  const path = __importStar(require("path"));
81
81
  const child_process_1 = require("child_process");
82
+ const session_id_1 = require("../session-id");
82
83
  const WATCHER_DIRNAME = 'watchers';
83
84
  // Matches a greprag watcher's command line in every launch shape (npm shim, bash
84
85
  // relauncher, the supervisor's CreateProcess re-invocation). Anchored on the
85
86
  // INVOKED BINARY (`greprag` or `index.js`) immediately followed by `inbox watch`
86
87
  // — never a bare `inbox watch` substring. Retained for the owner-pid resolver's
87
88
  // claude.exe match; the cleanup path no longer scans the process table at all.
88
- const CLAUDE_PROC_RE = /^claude(\.exe)?$/i;
89
+ const CLAUDE_PROC_RE = /^(claude|grok)(\.exe)?$/i;
89
90
  /** Default per-session watcher floor: keep this many freshest live watchers, reap
90
91
  * only the surplus above it, never below it. K=2 = the live one + one margin. */
91
92
  exports.DEFAULT_WATCHER_CAP = 2;
@@ -97,9 +98,26 @@ function watchersDir() {
97
98
  const h = grepragHome();
98
99
  return h ? path.join(h, WATCHER_DIRNAME) : null;
99
100
  }
100
- function pidfilePath(short) {
101
- const dir = watchersDir();
102
- return dir ? path.join(dir, `${short}.json`) : null;
101
+ function sessionKey(short) {
102
+ return (0, session_id_1.truncateSessionId)(short) || short;
103
+ }
104
+ /** Pidfile names that belong to the same session: canonical short (16-hex
105
+ * for UUIDv7, 8-hex otherwise) plus the raw/--session string. A Grok watch
106
+ * armed with `$GROK_SESSION_ID` (dashed UUID) used to write
107
+ * `<full-uuid>.json` while Stop's isLocallyArmed looked at `<16-hex>.json`
108
+ * → perpetual UNARMED nag + singleton-guard bounce. */
109
+ function matchingPidfileNames(short) {
110
+ const want = sessionKey(short);
111
+ const names = new Set();
112
+ if (want)
113
+ names.add(want);
114
+ if (short)
115
+ names.add(short);
116
+ for (const name of allShorts()) {
117
+ if (name === short || sessionKey(name) === want)
118
+ names.add(name);
119
+ }
120
+ return [...names];
103
121
  }
104
122
  /** True iff `pid` is a live process. `process.kill(pid, 0)` sends no signal — it
105
123
  * only probes existence. EPERM = exists but another user (alive); ESRCH = gone. */
@@ -114,20 +132,32 @@ function pidAlive(pid) {
114
132
  return e?.code === 'EPERM';
115
133
  }
116
134
  }
117
- /** Read a session's entry LIST. Back-compat: a legacy single-object pidfile reads
118
- * as a 1-element list. */
135
+ /** Read a session's entry LIST. Merges alias pidfiles (dashed UUID, 16-hex,
136
+ * raw --session). Back-compat: a legacy single-object pidfile reads as a
137
+ * 1-element list. */
119
138
  function readWatcherEntries(short) {
120
- try {
121
- const p = pidfilePath(short);
122
- if (!p)
123
- return [];
124
- const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
125
- const arr = Array.isArray(parsed) ? parsed : [parsed];
126
- return arr.filter((r) => !!r && typeof r.pid === 'number');
127
- }
128
- catch {
139
+ const dir = watchersDir();
140
+ if (!dir)
129
141
  return [];
142
+ const seen = new Set();
143
+ const out = [];
144
+ for (const name of matchingPidfileNames(short)) {
145
+ try {
146
+ const parsed = JSON.parse(fs.readFileSync(path.join(dir, `${name}.json`), 'utf-8'));
147
+ const arr = Array.isArray(parsed) ? parsed : [parsed];
148
+ for (const r of arr) {
149
+ if (!r || typeof r.pid !== 'number')
150
+ continue;
151
+ const rec = r;
152
+ if (seen.has(rec.pid))
153
+ continue;
154
+ seen.add(rec.pid);
155
+ out.push(rec);
156
+ }
157
+ }
158
+ catch { /* missing alias is fine */ }
130
159
  }
160
+ return out;
131
161
  }
132
162
  function writeWatcherEntries(short, entries) {
133
163
  try {
@@ -135,12 +165,21 @@ function writeWatcherEntries(short, entries) {
135
165
  if (!dir)
136
166
  return;
137
167
  fs.mkdirSync(dir, { recursive: true });
138
- const p = path.join(dir, `${short}.json`);
168
+ const key = sessionKey(short);
169
+ const canonical = path.join(dir, `${key}.json`);
170
+ for (const name of matchingPidfileNames(short)) {
171
+ if (name === key)
172
+ continue;
173
+ try {
174
+ fs.rmSync(path.join(dir, `${name}.json`), { force: true });
175
+ }
176
+ catch { /* best-effort */ }
177
+ }
139
178
  if (entries.length === 0) {
140
- fs.rmSync(p, { force: true });
179
+ fs.rmSync(canonical, { force: true });
141
180
  return;
142
181
  }
143
- fs.writeFileSync(p, JSON.stringify(entries));
182
+ fs.writeFileSync(canonical, JSON.stringify(entries));
144
183
  }
145
184
  catch { /* best-effort — a failed write only means re-arm, never a crash */ }
146
185
  }
@@ -155,12 +194,18 @@ function writeWatcherPidfile(short, pid, ownerPid) {
155
194
  function removeWatcherEntry(short, pid) {
156
195
  writeWatcherEntries(short, readWatcherEntries(short).filter(r => r.pid !== pid));
157
196
  }
158
- /** Remove a session's whole pidfile (legacy / full sweep). */
197
+ /** Remove a session's whole pidfile (legacy / full sweep), including aliases. */
159
198
  function removeWatcherPidfile(short) {
160
199
  try {
161
- const p = pidfilePath(short);
162
- if (p)
163
- fs.rmSync(p, { force: true });
200
+ const dir = watchersDir();
201
+ if (!dir)
202
+ return;
203
+ for (const name of matchingPidfileNames(short)) {
204
+ try {
205
+ fs.rmSync(path.join(dir, `${name}.json`), { force: true });
206
+ }
207
+ catch { /* best-effort */ }
208
+ }
164
209
  }
165
210
  catch { /* best-effort */ }
166
211
  }
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /** Grok Build session files + the recap sidecar SessionStart can write
3
+ * (Grok ignores SessionStart stdout, so the always-on rule points here).
4
+ * adr: adr/grok-platform.md */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.grokSessionDir = grokSessionDir;
40
+ exports.readLatestGrokUserPrompt = readLatestGrokUserPrompt;
41
+ exports.grokSidecarPath = grokSidecarPath;
42
+ exports.writeGrokSidecar = writeGrokSidecar;
43
+ exports.appendGrokSidecar = appendGrokSidecar;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const os = __importStar(require("os"));
47
+ function grokHome() {
48
+ if (process.env.GROK_HOME)
49
+ return process.env.GROK_HOME;
50
+ return path.join(os.homedir(), '.grok');
51
+ }
52
+ function grokSessionDir(cwd, sessionId) {
53
+ const encoded = encodeURIComponent(path.resolve(cwd));
54
+ return path.join(grokHome(), 'sessions', encoded, sessionId);
55
+ }
56
+ function extractText(content) {
57
+ if (typeof content === 'string')
58
+ return content.trim();
59
+ if (!Array.isArray(content))
60
+ return '';
61
+ return content
62
+ .filter((b) => b?.type === 'text' && typeof b.text === 'string')
63
+ .map((b) => String(b.text))
64
+ .join('\n')
65
+ .trim();
66
+ }
67
+ function unwrapUserQuery(text) {
68
+ const m = text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/i);
69
+ return (m?.[1] || text).trim();
70
+ }
71
+ /** Latest real user prompt from Grok chat_history.jsonl. */
72
+ function readLatestGrokUserPrompt(cwd, sessionId) {
73
+ if (!sessionId)
74
+ return '';
75
+ const file = path.join(grokSessionDir(cwd, sessionId), 'chat_history.jsonl');
76
+ let raw;
77
+ try {
78
+ raw = fs.readFileSync(file, 'utf-8');
79
+ }
80
+ catch {
81
+ return '';
82
+ }
83
+ const lines = raw.split(/\n/);
84
+ for (let i = lines.length - 1; i >= 0; i--) {
85
+ const line = lines[i].trim();
86
+ if (!line)
87
+ continue;
88
+ let entry;
89
+ try {
90
+ entry = JSON.parse(line);
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ if (entry.type !== 'user')
96
+ continue;
97
+ const text = unwrapUserQuery(extractText(entry.content));
98
+ if (text)
99
+ return text;
100
+ }
101
+ return '';
102
+ }
103
+ function grokSidecarPath(short) {
104
+ return path.join(os.homedir(), '.greprag', 'grok-context', `${short}.md`);
105
+ }
106
+ function writeGrokSidecar(short, text) {
107
+ if (!short || !text)
108
+ return;
109
+ try {
110
+ const file = grokSidecarPath(short);
111
+ fs.mkdirSync(path.dirname(file), { recursive: true });
112
+ fs.writeFileSync(file, text);
113
+ }
114
+ catch { /* sidecar is best-effort */ }
115
+ }
116
+ function appendGrokSidecar(short, text) {
117
+ if (!short || !text)
118
+ return;
119
+ try {
120
+ const file = grokSidecarPath(short);
121
+ fs.mkdirSync(path.dirname(file), { recursive: true });
122
+ fs.appendFileSync(file, (fs.existsSync(file) ? '\n\n' : '') + text);
123
+ }
124
+ catch { /* sidecar is best-effort */ }
125
+ }
package/dist/harness.js CHANGED
@@ -8,6 +8,7 @@ exports.REPAIR_TARGET_HARNESSES = [
8
8
  'claude-code',
9
9
  'codex',
10
10
  'opencode',
11
+ 'grok',
11
12
  'all',
12
13
  ];
13
14
  function normalizeHarness(raw) {
@@ -20,6 +21,8 @@ function normalizeHarness(raw) {
20
21
  return 'codex';
21
22
  if (v === 'opencode' || v === 'open-code' || v === 'open_code')
22
23
  return 'opencode';
24
+ if (v === 'grok' || v === 'grok-build' || v === 'grok_build')
25
+ return 'grok';
23
26
  if (v === 'all' || v === 'any' || v === 'universal')
24
27
  return 'all';
25
28
  return null;
@@ -30,6 +33,9 @@ function inferCurrentHarness() {
30
33
  if (h && h !== 'all')
31
34
  return h;
32
35
  }
36
+ // Grok sets CLAUDE_PROJECT_DIR as a compat alias. Detect Grok BEFORE Claude.
37
+ if (process.env.GROK_SESSION_ID || process.env.GROK_HOOK_EVENT)
38
+ return 'grok';
33
39
  if (process.env.CODEX_THREAD_ID)
34
40
  return 'codex';
35
41
  if (process.env.GREPRAG_OPENCODE_SESSION_ID || process.env.OPENCODE_SESSION_ID)
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /** Once-per-event stamp so leaked Claude greprag hooks + dedicated
3
+ * ~/.grok/hooks/greprag.json cannot double-store / double-recap.
4
+ * adr: adr/grok-platform.md */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.claimHookOnce = claimHookOnce;
40
+ const fs = __importStar(require("fs"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const TTL_MS = 60_000;
44
+ function stampPath(sessionId, event, sub, turnId) {
45
+ const safe = [sessionId, event, sub, turnId].join('-').replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 180);
46
+ return path.join(os.homedir(), '.greprag', 'hook-once', `${safe}.json`);
47
+ }
48
+ /** True = this process should run the subcommand. False = duplicate, skip. */
49
+ function claimHookOnce(sessionId, event, subcommand, turnId) {
50
+ if (!sessionId)
51
+ return true;
52
+ const key = turnId || 'noturn';
53
+ const file = stampPath(sessionId, event || 'unknown', subcommand, key);
54
+ try {
55
+ fs.mkdirSync(path.dirname(file), { recursive: true });
56
+ if (fs.existsSync(file)) {
57
+ const age = Date.now() - fs.statSync(file).mtimeMs;
58
+ if (age < TTL_MS)
59
+ return false;
60
+ }
61
+ fs.writeFileSync(file, JSON.stringify({ ts: Date.now(), subcommand }));
62
+ return true;
63
+ }
64
+ catch {
65
+ return true;
66
+ }
67
+ }
@@ -33,12 +33,55 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.normalizeHookInput = normalizeHookInput;
37
+ exports.isGrokProcess = isGrokProcess;
38
+ exports.isShellTool = isShellTool;
36
39
  exports.ensureEnv = ensureEnv;
37
40
  exports.getHookConfig = getHookConfig;
38
41
  exports.apiCall = apiCall;
39
42
  exports.writeAdditionalContext = writeAdditionalContext;
40
43
  const fs = __importStar(require("fs"));
41
44
  const path = __importStar(require("path"));
45
+ function asString(v) {
46
+ return typeof v === 'string' && v ? v : undefined;
47
+ }
48
+ function asObj(v) {
49
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : undefined;
50
+ }
51
+ /** Grok sends camelCase; Claude/Codex send snake_case. One bag for every hook.
52
+ * adr: adr/grok-platform.md */
53
+ function normalizeHookInput(raw) {
54
+ const r = (raw && typeof raw === 'object') ? raw : {};
55
+ const event = asString(r.hook_event_name) || asString(r.hookEventName)
56
+ || asString(process.env.GROK_HOOK_EVENT);
57
+ const session = asString(r.session_id) || asString(r.sessionId)
58
+ || asString(process.env.GROK_SESSION_ID)
59
+ || asString(process.env.CLAUDE_CODE_SESSION_ID)
60
+ || asString(process.env.CODEX_THREAD_ID);
61
+ const cwd = asString(r.cwd) || asString(r.workspaceRoot)
62
+ || asString(process.env.GROK_WORKSPACE_ROOT);
63
+ const toolInput = asObj(r.tool_input) || asObj(r.toolInput);
64
+ return {
65
+ session_id: session,
66
+ turn_id: asString(r.turn_id) || asString(r.promptId) || asString(r.prompt_id),
67
+ transcript_path: asString(r.transcript_path) || asString(r.transcriptPath),
68
+ cwd,
69
+ hook_event_name: event,
70
+ prompt: asString(r.prompt),
71
+ model: asString(r.model),
72
+ last_assistant_message: asString(r.last_assistant_message) || asString(r.lastAssistantMessage),
73
+ tool_name: asString(r.tool_name) || asString(r.toolName),
74
+ tool_input: toolInput,
75
+ stop_hook_active: r.stop_hook_active === true || r.stopHookActive === true,
76
+ };
77
+ }
78
+ function isGrokProcess() {
79
+ return !!(process.env.GROK_SESSION_ID || process.env.GROK_HOOK_EVENT);
80
+ }
81
+ function isShellTool(name) {
82
+ const n = (name || '').toLowerCase();
83
+ return n === 'bash' || n === 'run_terminal_command' || n === 'shell';
84
+ }
42
85
  const API_URL_DEFAULT = 'https://api.greprag.com';
43
86
  function loadEnvFile(filePath) {
44
87
  try {