greprag 5.73.2 → 5.73.4

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/dist/hook.js CHANGED
@@ -49,6 +49,8 @@ const project_anchor_1 = require("./project-anchor");
49
49
  const mechanic_role_1 = require("./mechanic-role");
50
50
  const assistant_doctrine_1 = require("./assistant-doctrine");
51
51
  const session_id_1 = require("./session-id");
52
+ const hook_runtime_1 = require("./hook-runtime");
53
+ const codex_prompt_cache_1 = require("./codex-prompt-cache");
52
54
  // adr: adr/memory-provenance-capture.md — classify harness-injected user text
53
55
  // (skill bodies, continuation summaries, chip prompts) pre-LLM at capture and
54
56
  // elide it so it never lands as a searchable turn node.
@@ -80,7 +82,6 @@ const guard_1 = require("./guard");
80
82
  const reminder_registry_1 = require("./commands/reminder-registry");
81
83
  const inbox_primer_reminder_1 = require("./commands/inbox-primer-reminder");
82
84
  const skill_activation_manifest_1 = require("./skill-activation-manifest");
83
- const friction_reminder_1 = require("./commands/friction-reminder");
84
85
  const worktree_state_1 = require("./worktree-state");
85
86
  // Ingress-trigger bridge, Adapter A (LOCAL/state half) — the Stop hook computes
86
87
  // the deterministic state values (stress / turnCount / keyword-seen) from the
@@ -130,69 +131,14 @@ const ship_through_gate_1 = require("./commands/ship-through-gate");
130
131
  // continuity, distilled server-side, transitions applied to the local store.
131
132
  const procedure_watch_1 = require("./procedure-watch");
132
133
  const inline_atom_hook_1 = require("./inline-atom-hook");
134
+ const inline_atom_background_1 = require("./inline-atom-background");
135
+ const reminder_injector_1 = require("./reminder-injector");
133
136
  const skill_landing_1 = require("./skill-landing");
134
137
  const skill_mirror_client_1 = require("./skill-mirror-client");
135
138
  const doc_mirror_client_1 = require("./doc-mirror-client");
136
139
  const equipped_loadouts_cache_1 = require("./equipped-loadouts-cache");
137
140
  const codex_chip_hooks_1 = require("./codex-chip-hooks");
138
- const API_URL_DEFAULT = 'https://api.greprag.com';
139
141
  const MAX_FIELD_CHARS = 500_000; // safety cap per text field
140
- // ---------- Env + config ---------------------------------------------------
141
- function loadEnvFile(filePath) {
142
- try {
143
- const content = fs.readFileSync(filePath, 'utf-8');
144
- for (const line of content.split('\n')) {
145
- const trimmed = line.trim();
146
- if (!trimmed || trimmed.startsWith('#'))
147
- continue;
148
- const eqIdx = trimmed.indexOf('=');
149
- if (eqIdx < 1)
150
- continue;
151
- const key = trimmed.slice(0, eqIdx).trim();
152
- let value = trimmed.slice(eqIdx + 1).trim();
153
- if ((value.startsWith('"') && value.endsWith('"')) ||
154
- (value.startsWith("'") && value.endsWith("'"))) {
155
- value = value.slice(1, -1);
156
- }
157
- if (!process.env[key])
158
- process.env[key] = value;
159
- }
160
- }
161
- catch { /* file missing — fine */ }
162
- }
163
- function ensureEnv(cwd) {
164
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
165
- if (homeDir) {
166
- loadEnvFile(path.join(homeDir, '.greprag', '.env'));
167
- if (!process.env.GREPRAG_API_KEY)
168
- loadEnvFile(path.join(homeDir, '.env'));
169
- }
170
- if (!process.env.GREPRAG_API_KEY)
171
- loadEnvFile(path.join(cwd, '.env'));
172
- }
173
- function getConfig(cwd) {
174
- ensureEnv(cwd);
175
- return {
176
- apiUrl: process.env.GREPRAG_API_URL || API_URL_DEFAULT,
177
- apiKey: process.env.GREPRAG_API_KEY || '',
178
- enabled: process.env.MEMORY_HOOK_ENABLED === 'true',
179
- };
180
- }
181
- async function apiCall(url, apiKey, body) {
182
- const res = await fetch(url, {
183
- method: 'POST',
184
- headers: {
185
- 'Authorization': `Bearer ${apiKey}`,
186
- 'Content-Type': 'application/json',
187
- },
188
- body: JSON.stringify(body),
189
- });
190
- if (!res.ok) {
191
- process.stderr.write(`[greprag-hook] API ${res.status}: ${await res.text()}\n`);
192
- return null;
193
- }
194
- return res.json();
195
- }
196
142
  function extractText(content) {
197
143
  if (typeof content === 'string')
198
144
  return content.trim();
@@ -397,67 +343,6 @@ function parseLatestTurn(transcriptPath) {
397
343
  provenance: (0, turn_provenance_1.classifyUserText)(userPrompt),
398
344
  };
399
345
  }
400
- function codexCacheDir() {
401
- const home = process.env.HOME || process.env.USERPROFILE || '';
402
- if (!home)
403
- return null;
404
- return path.join(home, '.greprag', 'codex');
405
- }
406
- function promptCachePath(sessionId, turnId) {
407
- const dir = codexCacheDir();
408
- if (!dir || !sessionId)
409
- return null;
410
- const raw = turnId || sessionId;
411
- const safe = raw.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 160);
412
- return path.join(dir, `${safe}.json`);
413
- }
414
- function latestPromptCachePath(sessionId) {
415
- const dir = codexCacheDir();
416
- if (!dir || !sessionId)
417
- return null;
418
- const safe = sessionId.replace(/[^a-zA-Z0-9_.-]/g, '_').slice(0, 160);
419
- return path.join(dir, `${safe}.latest.json`);
420
- }
421
- function cacheCodexPrompt(input) {
422
- if (typeof input.prompt !== 'string' || !input.prompt.trim() || !input.session_id)
423
- return;
424
- const entry = {
425
- sessionId: input.session_id,
426
- turnId: input.turn_id || null,
427
- prompt: input.prompt,
428
- model: input.model || null,
429
- cwd: input.cwd || process.cwd(),
430
- createdAt: new Date().toISOString(),
431
- };
432
- const paths = [
433
- promptCachePath(input.session_id, input.turn_id),
434
- latestPromptCachePath(input.session_id),
435
- ].filter((p) => !!p);
436
- for (const file of paths) {
437
- try {
438
- const dir = path.dirname(file);
439
- if (!fs.existsSync(dir))
440
- fs.mkdirSync(dir, { recursive: true });
441
- fs.writeFileSync(file, JSON.stringify(entry, null, 2) + '\n');
442
- }
443
- catch { /* prompt cache is best-effort */ }
444
- }
445
- }
446
- function readCodexPromptCache(input) {
447
- const paths = [
448
- promptCachePath(input.session_id, input.turn_id),
449
- latestPromptCachePath(input.session_id),
450
- ].filter((p) => !!p);
451
- for (const file of paths) {
452
- try {
453
- const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
454
- if (parsed && typeof parsed.prompt === 'string' && parsed.prompt.trim())
455
- return parsed;
456
- }
457
- catch { /* try next */ }
458
- }
459
- return null;
460
- }
461
346
  /** Walk subagent transcripts under <session>/subagents/ and pull tool_use
462
347
  * blocks that fired during the current turn. Returns flattened ToolCall[]
463
348
  * augmented with _output where a matching tool_result exists in the same
@@ -1125,7 +1010,7 @@ async function fetchDocPointers(apiUrl, apiKey, anchor, cwd) {
1125
1010
  // ---------- Store ---------------------------------------------------------
1126
1011
  async function store(input, source = 'claude-code') {
1127
1012
  const cwd = input.cwd || process.cwd();
1128
- const cfg = getConfig(cwd);
1013
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
1129
1014
  if (!cfg.enabled || !cfg.apiKey)
1130
1015
  return;
1131
1016
  const anchor = (0, project_anchor_1.readAnchor)(cwd);
@@ -1135,7 +1020,7 @@ async function store(input, source = 'claude-code') {
1135
1020
  return;
1136
1021
  const turn = parseLatestTurn(input.transcript_path);
1137
1022
  if (source === 'codex' && (!turn.userPrompt || !turn.agentResponse)) {
1138
- const cached = readCodexPromptCache(input);
1023
+ const cached = (0, codex_prompt_cache_1.readCodexPromptCache)(input);
1139
1024
  if (!turn.userPrompt && cached) {
1140
1025
  turn.userPrompt = cached.prompt;
1141
1026
  turn.provenance = (0, turn_provenance_1.classifyUserText)(cached.prompt);
@@ -1185,7 +1070,7 @@ async function store(input, source = 'claude-code') {
1185
1070
  }));
1186
1071
  const toolCalls = (0, secret_scrubber_1.scrubObject)(toolCallsForApi, redaction);
1187
1072
  const artifactRefs = shipEvents.map(ev => `${ev.artifactType}:${ev.artifactId.slice(0, 12)}`);
1188
- await apiCall(`${cfg.apiUrl}/v1/memory/turn`, cfg.apiKey, {
1073
+ await (0, hook_runtime_1.apiCall)(`${cfg.apiUrl}/v1/memory/turn`, cfg.apiKey, {
1189
1074
  projectId: anchor.projectId,
1190
1075
  projectName: anchor.projectName,
1191
1076
  branch,
@@ -1209,7 +1094,7 @@ async function store(input, source = 'claude-code') {
1209
1094
  const docEvents = collectDocEvents(turn.filesTouched, cwd);
1210
1095
  if (docEvents.length > 0) {
1211
1096
  try {
1212
- await apiCall(`${cfg.apiUrl}/v1/docptr/${anchor.projectId}/events`, cfg.apiKey, {
1097
+ await (0, hook_runtime_1.apiCall)(`${cfg.apiUrl}/v1/docptr/${anchor.projectId}/events`, cfg.apiKey, {
1213
1098
  projectName: anchor.projectName,
1214
1099
  events: docEvents,
1215
1100
  });
@@ -1222,7 +1107,7 @@ async function store(input, source = 'claude-code') {
1222
1107
  try {
1223
1108
  const mirror = (0, doc_mirror_client_1.collectTouchedDocMirrorDocs)(turn.filesTouched, cwd);
1224
1109
  if (mirror.docs.length > 0) {
1225
- await apiCall(`${cfg.apiUrl}/v1/doc/${anchor.projectId}/mirror`, cfg.apiKey, {
1110
+ await (0, hook_runtime_1.apiCall)(`${cfg.apiUrl}/v1/doc/${anchor.projectId}/mirror`, cfg.apiKey, {
1226
1111
  projectName: anchor.projectName,
1227
1112
  docs: mirror.docs.map(({ contentHash: _contentHash, ...doc }) => doc),
1228
1113
  mode: 'upsert',
@@ -1240,13 +1125,25 @@ async function store(input, source = 'claude-code') {
1240
1125
  }
1241
1126
  catch { /* fail-open — a procedure outage never fails the Stop hook */ }
1242
1127
  }
1243
- // Inline-Atom Episode Capture (docs/capture-system.md): new backtick
1244
- // candidates, active episodes, and replay receipts share one Flash-Lite
1245
- // observation call. Closed episodes alone reach the Flash distiller.
1246
- try {
1247
- await (0, inline_atom_hook_1.observeInlineAtoms)(cfg, anchor, input.session_id, userPrompt, agentResponse, toolCalls);
1128
+ // Inline-Atom Episode Capture (docs/capture-system.md): the detector/distiller
1129
+ // is network-backed. Codex Stop spools it to a detached worker so the app's
1130
+ // UI-critical Stop path does not wait on judge/distill calls; Claude Code keeps
1131
+ // the prior synchronous lifecycle.
1132
+ if (source === 'codex') {
1133
+ (0, inline_atom_background_1.spawnInlineAtomObserver)({
1134
+ cwd,
1135
+ sessionId: input.session_id,
1136
+ userPrompt,
1137
+ agentResponse,
1138
+ toolCalls,
1139
+ });
1140
+ }
1141
+ else {
1142
+ try {
1143
+ await (0, inline_atom_hook_1.observeInlineAtoms)(cfg, anchor, input.session_id, userPrompt, agentResponse, toolCalls);
1144
+ }
1145
+ catch { /* fail-open — an inline-atom miss never fails the Stop hook */ }
1248
1146
  }
1249
- catch { /* fail-open — an inline-atom miss never fails the Stop hook */ }
1250
1147
  // Skill mirror (docs/load-system.md Tier 3): a skill-load turn shadows that
1251
1148
  // skill's current markdown into the tenant's load store — upload ONLY on
1252
1149
  // change (local hash state), so an unchanged skill costs zero network.
@@ -1304,25 +1201,6 @@ async function fetchCorpusApiDocs(apiUrl, apiKey) {
1304
1201
  return [];
1305
1202
  }
1306
1203
  }
1307
- /** Fetch this session's OWN unread count — messages addressed to `to_session_id =
1308
- * <short>` (mail a peer or the operator sent to THIS session). Safe + non-eavesdrop:
1309
- * reads only this session's mail, NOT the project-wide count the v5.6.1 removal retired
1310
- * (that count betrayed a sibling session's private DMs). Powers the watcher-arm module's
1311
- * grounded "a peer messaged you" nag. Returns 0 on any error. adr: adr/session-id-awareness.md */
1312
- async function fetchSessionUnread(apiUrl, apiKey, short) {
1313
- try {
1314
- const res = await fetch(`${apiUrl}/v1/inbox?count_only=1&session_id=${encodeURIComponent(short)}`, {
1315
- headers: { 'Authorization': `Bearer ${apiKey}` },
1316
- });
1317
- if (!res.ok)
1318
- return 0;
1319
- const data = await res.json();
1320
- return data.unread_count || 0;
1321
- }
1322
- catch {
1323
- return 0;
1324
- }
1325
- }
1326
1204
  /** Identity-drift warning rate limiter.
1327
1205
  *
1328
1206
  * The drift warning ("stored ID ≠ git-derived ID, run `greprag doctor`") used
@@ -1392,7 +1270,7 @@ function writeRecapOutput(text, mode) {
1392
1270
  if (!text)
1393
1271
  return;
1394
1272
  if (mode === 'additionalContext') {
1395
- writeAdditionalContext('SessionStart', text);
1273
+ (0, hook_runtime_1.writeAdditionalContext)('SessionStart', text);
1396
1274
  return;
1397
1275
  }
1398
1276
  process.stdout.write(text);
@@ -1477,6 +1355,9 @@ async function checkForUpdate() {
1477
1355
  * Storage and display are both UTC. The agent can compute local time itself
1478
1356
  * if needed — a server-side UTC display avoids straddle-day confusion. */
1479
1357
  async function recap(input, mode = 'plain', opts = {}) {
1358
+ const platform = opts.platform || 'claude-code';
1359
+ const hasLocalMonitorLifecycle = platform === 'claude-code';
1360
+ let sessionOwnerPid = null;
1480
1361
  // SessionStart watcher cleanup (best-effort, side-effect only — never writes to
1481
1362
  // stdout, which carries this hook's additionalContext JSON). Three snapshot-free
1482
1363
  // passes: (1) refresh THIS session's owner record (current claude.exe PID) FIRST,
@@ -1485,21 +1366,23 @@ async function recap(input, mode = 'plain', opts = {}) {
1485
1366
  // only the surplus, never below K (no death-detection → no false-kill); (3) GC —
1486
1367
  // reap a session whose owner has stayed dead past the grace window (the hard-
1487
1368
  // crash leak EPIPE can't catch). adr: adr/monitor-resilience.md
1488
- try {
1489
- const short = (0, session_id_1.truncateSessionId)(input.session_id);
1490
- if (short)
1491
- (0, watcher_registry_1.resolveClaudeOwnerPid)(short); // refresh this session's owner FIRST
1492
- const capped = (0, watcher_registry_1.reapOrphanWatchers)();
1493
- const gc = (0, watcher_registry_1.gcDeadSessions)();
1494
- const n = capped.killed.length + gc.killed.length;
1495
- if (n > 0) {
1496
- process.stderr.write(`[greprag] watcher cleanup: capped ${capped.killed.length}, gc'd ${gc.killed.length} ` +
1497
- `[${[...capped.killed, ...gc.killed].join(', ')}]\n`);
1369
+ if (hasLocalMonitorLifecycle) {
1370
+ try {
1371
+ const short = (0, session_id_1.truncateSessionId)(input.session_id);
1372
+ if (short)
1373
+ sessionOwnerPid = (0, watcher_registry_1.resolveClaudeOwnerPid)(short); // refresh this session's owner FIRST
1374
+ const capped = (0, watcher_registry_1.reapOrphanWatchers)();
1375
+ const gc = (0, watcher_registry_1.gcDeadSessions)();
1376
+ const n = capped.killed.length + gc.killed.length;
1377
+ if (n > 0) {
1378
+ process.stderr.write(`[greprag] watcher cleanup: capped ${capped.killed.length}, gc'd ${gc.killed.length} ` +
1379
+ `[${[...capped.killed, ...gc.killed].join(', ')}]\n`);
1380
+ }
1498
1381
  }
1382
+ catch { /* cleanup is best-effort — never block session start */ }
1499
1383
  }
1500
- catch { /* cleanup is best-effort — never block session start */ }
1501
1384
  const cwd = input.cwd || process.cwd();
1502
- const cfg = getConfig(cwd);
1385
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
1503
1386
  if (!cfg.enabled || !cfg.apiKey)
1504
1387
  return;
1505
1388
  // Ensure the machine's desk-line is up (idempotent singleton). One per machine,
@@ -1527,10 +1410,8 @@ async function recap(input, mode = 'plain', opts = {}) {
1527
1410
  // Mechanic matchset boot pull (D5) — rides the existing SessionStart call
1528
1411
  // (D1: no new round trips). Best-effort: a fetch failure leaves any prior
1529
1412
  // cache in place and never blocks the recap.
1530
- try {
1531
- await (0, guard_1.fetchAndCacheMatchset)(cfg.apiUrl, cfg.apiKey, anchor.projectId, opts.platform || 'claude-code');
1532
- }
1533
- catch { /* fail-open */ }
1413
+ const matchsetPromise = (0, guard_1.fetchAndCacheMatchset)(cfg.apiUrl, cfg.apiKey, anchor.projectId, platform)
1414
+ .catch(() => null);
1534
1415
  // Setup warning — surfaced at session start when the project's identity is
1535
1416
  // either unstable (path-hash fallback) OR drifting (file UUID differs from
1536
1417
  // what git would derive, indicating an old random UUID minted before git
@@ -1573,22 +1454,22 @@ async function recap(input, mode = 'plain', opts = {}) {
1573
1454
  // Corpus-announce signal — the names of the api-docs-tagged reference banks on
1574
1455
  // tap, so the corpus module can tell the agent to search them before answering
1575
1456
  // from training data. Best-effort; empty → the announce stays silent.
1576
- const corpusApiDocs = await fetchCorpusApiDocs(cfg.apiUrl, cfg.apiKey);
1457
+ const corpusApiDocsPromise = fetchCorpusApiDocs(cfg.apiUrl, cfg.apiKey);
1577
1458
  // Doc-pointer announce signal (docs/doc-pointer-system.md) — one reconcile+list
1578
1459
  // round trip: vanished docs go stale server-side, active pointers come back for
1579
1460
  // the "core documents" block. Best-effort; empty → the announce stays silent.
1580
- const docPointers = await fetchDocPointers(cfg.apiUrl, cfg.apiKey, anchor, cwd);
1461
+ const docPointersPromise = fetchDocPointers(cfg.apiUrl, cfg.apiKey, anchor, cwd);
1581
1462
  // Enrichment-health signal (fix c2eb8777) — the live Gemini probe verdict.
1582
1463
  // Null when healthy or unreachable; a positive failure fires the outage announce.
1583
- const enrichmentDown = await fetchEnrichmentDown(cfg.apiUrl, cfg.apiKey);
1464
+ const enrichmentDownPromise = fetchEnrichmentDown(cfg.apiUrl, cfg.apiKey);
1584
1465
  // Skill Learning Loop landing leg (docs/skill-learning-loop.md): pending
1585
1466
  // gains → auto-land references into learnings docs, hold rule/scope
1586
1467
  // proposals for the announce. Best-effort; undefined → announce silent.
1587
- const skillGains = await fetchAndLandSkillGains(cfg.apiUrl, cfg.apiKey, anchor, cwd);
1468
+ const skillGainsPromise = fetchAndLandSkillGains(cfg.apiUrl, cfg.apiKey, anchor, cwd);
1588
1469
  // Tier-3 skill activation manifest (docs/load-system.md): fetch canonical
1589
1470
  // full descriptions, then omit skills this harness already advertises
1590
1471
  // natively. Best-effort; the renderer remains pure.
1591
- const mirroredSkills = await (async () => {
1472
+ const mirroredSkillsPromise = (async () => {
1592
1473
  try {
1593
1474
  const params = new URLSearchParams({ scope: 'effective', projectId: anchor.projectId });
1594
1475
  const res = await fetch(`${cfg.apiUrl}/v1/skillmirror?${params.toString()}`, {
@@ -1599,7 +1480,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1599
1480
  const data = await res.json();
1600
1481
  return (0, skill_activation_manifest_1.activationFromApiRows)({
1601
1482
  rows: data.skills || [],
1602
- platform: opts.platform || 'claude-code',
1483
+ platform,
1603
1484
  cwd,
1604
1485
  homeDir: process.env.HOME || process.env.USERPROFILE || '',
1605
1486
  });
@@ -1612,7 +1493,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1612
1493
  // self-inflating skill bundles. SessionStart fetch → the ONE advisor announce
1613
1494
  // line each; ALSO cached locally so the per-turn Match sensor reads it without
1614
1495
  // network. Best-effort; undefined/empty → the loadout-registrar stays silent.
1615
- const loadoutState = await (async () => {
1496
+ const loadoutStatePromise = (async () => {
1616
1497
  const empty = { decls: undefined, pending: [] };
1617
1498
  try {
1618
1499
  const res = await fetch(`${cfg.apiUrl}/v1/loadout/equipped`, {
@@ -1637,6 +1518,21 @@ async function recap(input, mode = 'plain', opts = {}) {
1637
1518
  return empty;
1638
1519
  }
1639
1520
  })();
1521
+ const updateAvailablePromise = checkForUpdate();
1522
+ const bodyPromise = (!opts.compact && anchor.sessionStartRecap)
1523
+ ? (0, opencode_plugin_helpers_1.buildRecapBody)(cfg.apiUrl, cfg.apiKey, anchor)
1524
+ : Promise.resolve('');
1525
+ const [, corpusApiDocs, docPointers, enrichmentDown, skillGains, mirroredSkills, loadoutState, updateAvailable, body,] = await Promise.all([
1526
+ matchsetPromise,
1527
+ corpusApiDocsPromise,
1528
+ docPointersPromise,
1529
+ enrichmentDownPromise,
1530
+ skillGainsPromise,
1531
+ mirroredSkillsPromise,
1532
+ loadoutStatePromise,
1533
+ updateAvailablePromise,
1534
+ bodyPromise,
1535
+ ]);
1640
1536
  const equippedLoadouts = loadoutState.decls;
1641
1537
  // Loadout version-gate deficiency (docs/loadout.md §Version gating step 3): a
1642
1538
  // pending loadout whose equip floor is ABOVE the installed CLI version can't be
@@ -1659,9 +1555,6 @@ async function recap(input, mode = 'plain', opts = {}) {
1659
1555
  const assistantDoctrine = (0, project_anchor_1.isAssistantProject)(anchor)
1660
1556
  ? (0, assistant_doctrine_1.buildAssistantDoctrineContext)(cwd)
1661
1557
  : null;
1662
- // Deficiency-gated upgrade announce: daily-cached npm check → tell the user to
1663
- // upgrade when behind. This is how a release actually propagates to everyone.
1664
- const updateAvailable = await checkForUpdate();
1665
1558
  // Active Procedure installs (adr/procedure-install-control-plane.md). Local,
1666
1559
  // operator-owned state; missing/corrupt installs fail closed to no announce.
1667
1560
  const procedureAnnounces = (() => {
@@ -1694,7 +1587,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1694
1587
  } })();
1695
1588
  const announceEnv = {
1696
1589
  short: announceShort, turnCount: 0, stress: 0, armed: false, sessionUnread: 0,
1697
- ownerPid: announceShort ? (0, watcher_registry_1.resolveClaudeOwnerPid)(announceShort) : null,
1590
+ ownerPid: hasLocalMonitorLifecycle ? sessionOwnerPid : null,
1698
1591
  alias: (0, session_id_1.readIdentityAlias)(),
1699
1592
  assistant: (() => { try {
1700
1593
  return (0, project_anchor_1.isAssistantProject)(anchor);
@@ -1704,7 +1597,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1704
1597
  } })(),
1705
1598
  mechanic: announceMechanic,
1706
1599
  mechanicLive: announceMechanicLive,
1707
- platform: opts.platform || 'claude-code',
1600
+ platform,
1708
1601
  projectName: anchor.projectName,
1709
1602
  setupWarning,
1710
1603
  assistantDoctrine,
@@ -1718,7 +1611,7 @@ async function recap(input, mode = 'plain', opts = {}) {
1718
1611
  updateAvailable,
1719
1612
  loadoutUpdateRequired,
1720
1613
  };
1721
- let announceReg = mechanicKilled() ? reminder_registry_1.REGISTRY.filter(m => m.id !== 'mechanic-friction') : reminder_registry_1.REGISTRY;
1614
+ let announceReg = (0, reminder_injector_1.mechanicKilled)() ? reminder_registry_1.REGISTRY.filter(m => m.id !== 'mechanic-friction') : reminder_registry_1.REGISTRY;
1722
1615
  if (!announceShort)
1723
1616
  announceReg = announceReg.filter(m => m.id !== 'watcher-arm');
1724
1617
  // PostCompact: a compaction wiped the once-loaded SessionStart announces from context.
@@ -1752,7 +1645,6 @@ async function recap(input, mode = 'plain', opts = {}) {
1752
1645
  // function the opencode plugin uses, so the body pushed to the system
1753
1646
  // prompt is byte-identical across harnesses. See adr/opencode-monitor-
1754
1647
  // relay.md 2026-06-06 (g).
1755
- const body = await (0, opencode_plugin_helpers_1.buildRecapBody)(cfg.apiUrl, cfg.apiKey, anchor);
1756
1648
  // No hourlies → no recap block. Setup warning / checkpoint hint still
1757
1649
  // surface via preamble if they're pending.
1758
1650
  if (!body) {
@@ -1805,30 +1697,22 @@ async function recap(input, mode = 'plain', opts = {}) {
1805
1697
  * here, so a separate SessionStart arm was pure redundancy). Reads only the
1806
1698
  * watcher registry — no inbox content/count — so the v5.6.1 leak stays closed.
1807
1699
  * Gated on greprag being configured. adr: adr/session-id-awareness.md */
1808
- function writeAdditionalContext(hookEventName, additionalContext) {
1809
- process.stdout.write(JSON.stringify({
1810
- hookSpecificOutput: {
1811
- hookEventName,
1812
- additionalContext,
1813
- },
1814
- }) + '\n');
1815
- }
1816
1700
  async function codexInbox(input, debounce = true) {
1817
- const cfg = getConfig(input.cwd || process.cwd());
1701
+ const cfg = (0, hook_runtime_1.getHookConfig)(input.cwd || process.cwd());
1818
1702
  const context = await (0, codex_steering_1.buildCodexInboxSteering)(input, cfg, { debounce });
1819
1703
  if (!context)
1820
1704
  return;
1821
- writeAdditionalContext(input.hook_event_name || 'PostToolUse', context);
1705
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'PostToolUse', context);
1822
1706
  }
1823
1707
  function codexPermissionContext(input) {
1824
1708
  const context = (0, codex_hook_events_1.buildCodexPermissionContext)(input);
1825
1709
  if (context)
1826
- writeAdditionalContext(input.hook_event_name || 'PermissionRequest', context);
1710
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'PermissionRequest', context);
1827
1711
  }
1828
1712
  function codexSubagentStart(input) {
1829
1713
  const context = (0, codex_hook_events_1.recordCodexSubagentStart)(input);
1830
1714
  if (context)
1831
- writeAdditionalContext(input.hook_event_name || 'SubagentStart', context);
1715
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'SubagentStart', context);
1832
1716
  }
1833
1717
  /** UserPromptSubmit — Procedure System trigger leg (docs/procedure-system.md
1834
1718
  * §V1 LEARN-leg build spec, component 2). Two paths off a Tier-1 trigger hit:
@@ -1862,7 +1746,7 @@ async function procedureCheck(input) {
1862
1746
  return;
1863
1747
  const m = raw;
1864
1748
  if (m && m.procedure.status !== 'stale') {
1865
- writeAdditionalContext(input.hook_event_name || 'UserPromptSubmit', (0, procedure_1.buildProcedureInjection)(m.procedure));
1749
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'UserPromptSubmit', (0, procedure_1.buildProcedureInjection)(m.procedure));
1866
1750
  (0, procedure_watch_1.openWatchIfIdle)(anchor.projectId, m.procedure.verb, 'REPLAY');
1867
1751
  return;
1868
1752
  }
@@ -1879,14 +1763,14 @@ async function procedureCheck(input) {
1879
1763
  }
1880
1764
  async function notify(input, source = 'claude-code', chipContext) {
1881
1765
  if (source === 'codex')
1882
- cacheCodexPrompt(input);
1766
+ (0, codex_prompt_cache_1.cacheCodexPrompt)(input);
1883
1767
  const duplicateDelegation = source === 'codex'
1884
1768
  ? (0, codex_delegation_dedupe_1.checkCodexDelegationDuplicate)(input)
1885
1769
  : null;
1886
- const cfg = getConfig(input.cwd || process.cwd());
1770
+ const cfg = (0, hook_runtime_1.getHookConfig)(input.cwd || process.cwd());
1887
1771
  if (!cfg.enabled || !cfg.apiKey) { // unconfigured → local-only checks
1888
1772
  if (duplicateDelegation)
1889
- writeAdditionalContext('UserPromptSubmit', duplicateDelegation);
1773
+ (0, hook_runtime_1.writeAdditionalContext)('UserPromptSubmit', duplicateDelegation);
1890
1774
  return;
1891
1775
  }
1892
1776
  const short = (0, session_id_1.truncateSessionId)(input.session_id);
@@ -1908,11 +1792,11 @@ async function notify(input, source = 'claude-code', chipContext) {
1908
1792
  const context = await (0, codex_steering_1.buildCodexInboxSteering)(input, cfg, {
1909
1793
  excludeMessageIds: liveMessageId ? [liveMessageId] : [],
1910
1794
  });
1911
- const reminders = await injectReminders(input, cfg, short, 'codex');
1795
+ const reminders = await (0, reminder_injector_1.injectReminders)(input, cfg, short, 'codex');
1912
1796
  const inlineAtoms = (0, inline_atom_hook_1.buildPendingInlineAtomContext)(input, short);
1913
1797
  const parts = [duplicateDelegation, chipContext, context, reminders, inlineAtoms].filter((p) => !!p);
1914
1798
  if (parts.length)
1915
- writeAdditionalContext('UserPromptSubmit', parts.join('\n\n'));
1799
+ (0, hook_runtime_1.writeAdditionalContext)('UserPromptSubmit', parts.join('\n\n'));
1916
1800
  return;
1917
1801
  }
1918
1802
  // Reminder-interrupt registry container (docs/reminder-interrupt.md §Registry spec):
@@ -1920,181 +1804,11 @@ async function notify(input, source = 'claude-code', chipContext) {
1920
1804
  // proven-registered `notify` hook — so arming never depends on a freshly-merged
1921
1805
  // subcommand reaching the operator's settings. Fires whatever's deficient (stacking),
1922
1806
  // and auto-silences each module the instant its deficiency resolves.
1923
- const reminders = await injectReminders(input, cfg, short);
1807
+ const reminders = await (0, reminder_injector_1.injectReminders)(input, cfg, short);
1924
1808
  const inlineAtoms = (0, inline_atom_hook_1.buildPendingInlineAtomContext)(input, short);
1925
1809
  const parts = [reminders, inlineAtoms].filter((p) => !!p);
1926
1810
  if (parts.length)
1927
- writeAdditionalContext('UserPromptSubmit', parts.join('\n\n'));
1928
- }
1929
- /** Has the operator hit the Mechanic kill switch (`mechanic off` → the D8 panic
1930
- * switch file the guard dispatcher also checks first)? When set, the active-
1931
- * Mechanic pair (SessionStart announce + per-turn friction reminder) stays quiet,
1932
- * so the same switch that quiets the dispatcher quiets this pair too. Fail-quiet:
1933
- * any fs error reads as NOT killed (the pair keeps working). */
1934
- function mechanicKilled() {
1935
- try {
1936
- return fs.existsSync((0, guard_1.killSwitchPath)());
1937
- }
1938
- catch {
1939
- return false;
1940
- }
1941
- }
1942
- /** Local path for this project's friction-reminder fire-counter (Measurement,
1943
- * docs/reminder-interrupt.md). Sits beside the matchset cache under the shared
1944
- * `~/.greprag/state` dir. */
1945
- function frictionStatsPath(projectId) {
1946
- const home = process.env.HOME || process.env.USERPROFILE || '';
1947
- return path.join(home, '.greprag', 'state', `friction-reminder-${projectId}.json`);
1948
- }
1949
- /** Fire-counter I/O (the adherence DENOMINATOR). Reads the prior stats, tallies one
1950
- * fire via the PURE tallyFrictionFire, writes them back. Fully best-effort: a miss
1951
- * never blocks the turn. The numerator (Mechanic Chips actually spawned) lands
1952
- * via tallyMechanicSpawn from the spawn path — see the report's Measurement note. */
1953
- function recordFrictionFire(projectId, tier, turnCount) {
1954
- try {
1955
- const file = frictionStatsPath(projectId);
1956
- let prior = null;
1957
- try {
1958
- prior = JSON.parse(fs.readFileSync(file, 'utf-8'));
1959
- }
1960
- catch { /* none yet */ }
1961
- const next = (0, friction_reminder_1.tallyFrictionFire)(prior, tier, turnCount, new Date().toISOString());
1962
- const dir = path.dirname(file);
1963
- if (!fs.existsSync(dir))
1964
- fs.mkdirSync(dir, { recursive: true });
1965
- fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
1966
- }
1967
- catch { /* counter is best-effort — never block the turn */ }
1968
- }
1969
- function mechanicLiveNoticePath(projectId, sessionId) {
1970
- const home = process.env.HOME || process.env.USERPROFILE || '';
1971
- return path.join(home, '.greprag', 'state', `mechanic-live-notice-${projectId}-${sessionId}.json`);
1972
- }
1973
- function mechanicLiveNoticeDue(input) {
1974
- if (!input.projectId || !input.sessionId || !input.mechanicLive || input.mechanic)
1975
- return false;
1976
- const epoch = input.mechanicEpoch || `legacy:${input.mechanicSessionId || 'project-wide'}`;
1977
- const key = `${epoch}:${input.mechanicSessionId || 'project-wide'}`;
1978
- try {
1979
- const file = mechanicLiveNoticePath(input.projectId, input.sessionId);
1980
- try {
1981
- const prior = JSON.parse(fs.readFileSync(file, 'utf-8'));
1982
- if (prior.key === key)
1983
- return false;
1984
- }
1985
- catch { /* no prior notice */ }
1986
- const dir = path.dirname(file);
1987
- if (!fs.existsSync(dir))
1988
- fs.mkdirSync(dir, { recursive: true });
1989
- fs.writeFileSync(file, JSON.stringify({ key, at: new Date().toISOString() }, null, 2) + '\n');
1990
- return true;
1991
- }
1992
- catch {
1993
- return false;
1994
- }
1995
- }
1996
- /** The reminder-interrupt registry CONTAINER call (docs/reminder-interrupt.md §Registry
1997
- * spec) — invoked from the proven-registered `notify` hook (claude-code path). Assembles
1998
- * the live env (the ONLY i/o: armed via isLocallyArmed, this session's own unread, the
1999
- * Stop-stashed stress/turnCount, the front-desk envelope notice) and fires every module
2000
- * whose detector reports deficient: watcher-arm (unarmed → nag if a peer messaged you, else
2001
- * nudge; silent when armed), the stress-gated mechanic-friction, and front-desk (a soft
2002
- * "you've got mail" when a new envelope landed). The memory PRIMER announces at SessionStart;
2003
- * the auto-inject was dropped (knowledge injection, not a reminder — docs/reminder-interrupt.md).
2004
- * The announce-only modules (setup-warning / checkpoint / assistant-doctrine) stay silent per
2005
- * turn — their env signals are SessionStart-only. Stacking; emitted once. Fail-open — any miss
2006
- * → silent. The Mechanic kill switch drops ONLY the mechanic module; the rest keep working. */
2007
- async function injectReminders(input, cfg, short, platform = 'claude-code') {
2008
- try {
2009
- const cwd = input.cwd || process.cwd();
2010
- const anchor = (0, project_anchor_1.readAnchor)(cwd);
2011
- const codex = platform === 'codex';
2012
- const armed = codex ? true : (0, watcher_registry_1.isLocallyArmed)(short);
2013
- const sessionUnread = (codex || armed) ? 0 : await fetchSessionUnread(cfg.apiUrl, cfg.apiKey, short);
2014
- const ownerPid = (codex || armed) ? null : (0, watcher_registry_1.resolveClaudeOwnerPid)(short);
2015
- let assistant = false;
2016
- let mechanic = false;
2017
- let mechanicLive = false;
2018
- let mechanicEpoch = null;
2019
- let mechanicSessionId = null;
2020
- try {
2021
- assistant = (0, project_anchor_1.isAssistantProject)(anchor);
2022
- }
2023
- catch { /* fail-open */ }
2024
- try {
2025
- mechanicLive = (0, mechanic_role_1.isMechanicProject)(anchor);
2026
- mechanic = (0, mechanic_role_1.isMechanicSession)(anchor, short);
2027
- mechanicEpoch = (0, mechanic_role_1.readMechanicRoleEpoch)(anchor);
2028
- mechanicSessionId = (0, mechanic_role_1.readMechanicSessionId)(anchor);
2029
- }
2030
- catch { /* fail-open */ }
2031
- // Stress/turnCount from the Stop-stashed state bag (no LLM/network here).
2032
- const state = anchor.projectId
2033
- ? ((0, guard_1.readMatchsetCache)(anchor.projectId)?.stateValues || {})
2034
- : {};
2035
- const turnCount = typeof state.turnCount === 'number' ? state.turnCount : 0;
2036
- const stress = typeof state.stress === 'number' ? state.stress : 0;
2037
- const bootstrapFriction = state.bootstrapFriction === true ? {
2038
- level: typeof state.bootstrapFrictionLevel === 'number' ? state.bootstrapFrictionLevel : 2,
2039
- command: typeof state.bootstrapFrictionCommand === 'string' ? state.bootstrapFrictionCommand : null,
2040
- error: typeof state.bootstrapFrictionError === 'string' ? state.bootstrapFrictionError : null,
2041
- signal: typeof state.bootstrapFrictionSignal === 'string' ? state.bootstrapFrictionSignal : null,
2042
- repeats: typeof state.bootstrapFrictionRepeats === 'number' ? state.bootstrapFrictionRepeats : null,
2043
- } : null;
2044
- // NOTE: the per-turn memory AUTO-INJECT (recall-intent → search → surface a hit) was
2045
- // DROPPED 2026-06-22. It was knowledge-injection, NOT a behavioral reminder — a
2046
- // different (undeveloped) system that was jammed into this registry for lack of a slot.
2047
- // It also collided with the agent's own better-targeted `greprag memory search` (taught
2048
- // by the memory primer). The memory PRIMER (the announce) stays; only the auto-inject is
2049
- // gone. docs/reminder-interrupt.md (the landed map: reminder ≠ knowledge injection).
2050
- // Loadout Match sensor (docs/loadout.md): the prompt text is the `prompt`
2051
- // read surface; the equipped-loadout decls come from the SessionStart-written
2052
- // LOCAL cache (zero network on the hot path). Both undefined → registrar silent.
2053
- const promptText = typeof input.prompt === 'string' ? input.prompt : null;
2054
- const equippedLoadouts = (() => {
2055
- const cached = (0, equipped_loadouts_cache_1.readEquippedLoadouts)();
2056
- return cached.length ? cached : undefined;
2057
- })();
2058
- const env = {
2059
- short, turnCount, stress, armed, sessionUnread,
2060
- bootstrapFriction,
2061
- ownerPid, alias: (0, session_id_1.readIdentityAlias)(), assistant, mechanic, mechanicLive,
2062
- mechanicLiveNotice: mechanicLiveNoticeDue({
2063
- projectId: anchor.projectId,
2064
- sessionId: short,
2065
- mechanic,
2066
- mechanicLive,
2067
- mechanicEpoch,
2068
- mechanicSessionId,
2069
- }),
2070
- platform,
2071
- promptText,
2072
- equippedLoadouts,
2073
- };
2074
- // UserPromptSubmit evaluates the prompt-source modules only (command-source
2075
- // modules — the collision Match — run at PreToolUse). Kill switch drops ONLY
2076
- // the mechanic module — watcher-arm keeps working.
2077
- const base = (0, reminder_registry_1.promptModules)().filter(m => !codex || m.id !== 'watcher-arm');
2078
- const reg = mechanicKilled() ? base.filter(m => m.id !== 'mechanic-friction') : base;
2079
- const fired = (0, reminder_registry_1.collectReminders)(env, reg);
2080
- if (!fired.length)
2081
- return null;
2082
- // Authoring rule #4 — frame nag/nudge with the act-now directive envelope; ambient
2083
- // stays a soft pointer. Emit ONCE (stacking via join) so multiple due modules ride a
2084
- // single additionalContext.
2085
- const parts = fired.map(f => f.tier === 'ambient' ? f.line : (0, guard_1.wrapDirective)(f.line));
2086
- // Fire-counter (adherence denominator) for the fix-spawn reflex — best-effort.
2087
- // grepragOS Law 4 retired the old stress → mechanic_friction forwarding path:
2088
- // the hook reminds, the agent spawns one FIX chip per friction unit, and no
2089
- // hidden queue/catch-all row is emitted behind the working session.
2090
- const mf = fired.find(f => f.id === 'mechanic-friction');
2091
- if (mf && mf.tier !== 'ambient' && anchor.projectId) {
2092
- recordFrictionFire(anchor.projectId, mf.tier, turnCount);
2093
- }
2094
- return parts.join('\n\n');
2095
- }
2096
- catch { /* fail-open — a reminder miss never breaks the turn */ }
2097
- return null;
1811
+ (0, hook_runtime_1.writeAdditionalContext)('UserPromptSubmit', parts.join('\n\n'));
2098
1812
  }
2099
1813
  /** RETIRED 2026-06-20 — the per-turn reminder injection moved into the registry
2100
1814
  * container (`injectReminders`, called from `notify`), so watcher-arm + mechanic-friction
@@ -2120,13 +1834,13 @@ async function frictionReminder(_input) {
2120
1834
  * never blocks the turn. Unconfigured / no API key → silent. */
2121
1835
  async function mail(input) {
2122
1836
  const cwd = input.cwd || process.cwd();
2123
- const cfg = getConfig(cwd);
1837
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
2124
1838
  if (!cfg.enabled || !cfg.apiKey)
2125
1839
  return; // unconfigured → silent
2126
1840
  try {
2127
1841
  const auto = await maybeAutoSaveAttachments(cwd, cfg.apiUrl, cfg.apiKey);
2128
1842
  if (auto)
2129
- writeAdditionalContext(input.hook_event_name || 'UserPromptSubmit', auto);
1843
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'UserPromptSubmit', auto);
2130
1844
  }
2131
1845
  catch { /* drain is best-effort — never block a turn */ }
2132
1846
  }
@@ -2154,7 +1868,7 @@ async function maybeAutoSaveAttachments(cwd, apiUrl, apiKey) {
2154
1868
  * debugging: wire it as a second UserPromptSubmit hook to see arm state.
2155
1869
  * adr: adr/session-id-awareness.md */
2156
1870
  async function armCheck(input) {
2157
- const cfg = getConfig(input.cwd || process.cwd());
1871
+ const cfg = (0, hook_runtime_1.getHookConfig)(input.cwd || process.cwd());
2158
1872
  if (!cfg.enabled || !cfg.apiKey)
2159
1873
  return;
2160
1874
  const short = (0, session_id_1.truncateSessionId)(input.session_id);
@@ -2178,7 +1892,7 @@ async function armCheck(input) {
2178
1892
  * Fail-quiet: any miss → silent. docs/schematic-triggers.md */
2179
1893
  async function collisionCheck(input) {
2180
1894
  const cwd = input.cwd || process.cwd();
2181
- const cfg = getConfig(cwd);
1895
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
2182
1896
  if (!cfg.enabled || !cfg.apiKey)
2183
1897
  return; // unconfigured → silent
2184
1898
  const short = (0, session_id_1.truncateSessionId)(input.session_id);
@@ -2202,7 +1916,7 @@ async function collisionCheck(input) {
2202
1916
  alias: (0, session_id_1.readIdentityAlias)(),
2203
1917
  });
2204
1918
  if (context)
2205
- writeAdditionalContext(input.hook_event_name || 'SessionStart', context);
1919
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'SessionStart', context);
2206
1920
  }
2207
1921
  /** Inbox drain-on-arm — `greprag-hook drain`. Wire on SessionStart
2208
1922
  * (startup|resume|compact) = on arm. Drains THIS session's unread, session-
@@ -2216,7 +1930,7 @@ async function collisionCheck(input) {
2216
1930
  * never blocks SessionStart. adr: adr/monitor-resilience.md */
2217
1931
  async function drain(input) {
2218
1932
  const cwd = input.cwd || process.cwd();
2219
- const cfg = getConfig(cwd);
1933
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
2220
1934
  if (!cfg.enabled || !cfg.apiKey)
2221
1935
  return; // unconfigured → silent
2222
1936
  const short = (0, session_id_1.truncateSessionId)(input.session_id);
@@ -2224,7 +1938,7 @@ async function drain(input) {
2224
1938
  return; // no session id → silent
2225
1939
  const result = await (0, inbox_drain_1.runInboxDrain)({ session: short, apiUrl: cfg.apiUrl, apiKey: cfg.apiKey });
2226
1940
  if (result.context) {
2227
- writeAdditionalContext(input.hook_event_name || 'SessionStart', result.context);
1941
+ (0, hook_runtime_1.writeAdditionalContext)(input.hook_event_name || 'SessionStart', result.context);
2228
1942
  }
2229
1943
  }
2230
1944
  function validateChip(title, prompt) {
@@ -2303,7 +2017,7 @@ function guard(input) {
2303
2017
  function stateUpdate(input) {
2304
2018
  try {
2305
2019
  const cwd = input.cwd || process.cwd();
2306
- const cfg = getConfig(cwd);
2020
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
2307
2021
  if (!cfg.enabled || !cfg.apiKey)
2308
2022
  return;
2309
2023
  const anchor = (0, project_anchor_1.readAnchor)(cwd);
@@ -2334,7 +2048,7 @@ function stateUpdate(input) {
2334
2048
  * requires its local artifact/action attestation. docs/collision-schematic.md */
2335
2049
  async function coordinateGate(input, codex = false) {
2336
2050
  const cwd = input.cwd || process.cwd();
2337
- const cfg = getConfig(cwd);
2051
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
2338
2052
  if (!cfg.enabled || !cfg.apiKey)
2339
2053
  return; // unconfigured → silent
2340
2054
  const short = (0, session_id_1.truncateSessionId)(input.session_id);
@@ -2456,7 +2170,7 @@ async function coordinateGate(input, codex = false) {
2456
2170
  /** Detached background child: refetch the matchset + rewrite the cache. */
2457
2171
  async function guardRefresh(input) {
2458
2172
  const cwd = input.cwd || process.cwd();
2459
- const cfg = getConfig(cwd);
2173
+ const cfg = (0, hook_runtime_1.getHookConfig)(cwd);
2460
2174
  if (!cfg.apiKey)
2461
2175
  return;
2462
2176
  const anchor = (0, project_anchor_1.readAnchor)(cwd);
@@ -2466,7 +2180,7 @@ async function guardRefresh(input) {
2466
2180
  }
2467
2181
  /** Detached background child: drain tally spools to the server (202 route). */
2468
2182
  async function guardFlush(input) {
2469
- const cfg = getConfig(input.cwd || process.cwd());
2183
+ const cfg = (0, hook_runtime_1.getHookConfig)(input.cwd || process.cwd());
2470
2184
  if (!cfg.apiKey)
2471
2185
  return;
2472
2186
  await (0, guard_1.flushTallySpools)(cfg.apiUrl, cfg.apiKey);
@@ -2635,7 +2349,7 @@ async function main() {
2635
2349
  // whose output reaches the model. Points at the PNG archive.
2636
2350
  const pointer = await (0, archive_1.buildArchivePointer)(input);
2637
2351
  if (pointer)
2638
- writeAdditionalContext('SessionStart', pointer);
2352
+ (0, hook_runtime_1.writeAdditionalContext)('SessionStart', pointer);
2639
2353
  }
2640
2354
  else if (subcommand === 'codex-store') {
2641
2355
  await store(input, 'codex');