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/codex-fast-hook.js +9 -4
- package/dist/codex-fast-hook.js.map +1 -1
- package/dist/codex-notify-hook.d.ts +2 -0
- package/dist/codex-notify-hook.js +141 -0
- package/dist/codex-notify-hook.js.map +1 -0
- package/dist/codex-prompt-cache.d.ts +11 -0
- package/dist/codex-prompt-cache.js +105 -0
- package/dist/codex-prompt-cache.js.map +1 -0
- package/dist/codex-steering.js +4 -4
- package/dist/codex-steering.js.map +1 -1
- package/dist/commands/init.js +31 -7
- package/dist/commands/init.js.map +1 -1
- package/dist/hook-runtime.d.ts +22 -0
- package/dist/hook-runtime.js +108 -0
- package/dist/hook-runtime.js.map +1 -0
- package/dist/hook.js +96 -382
- package/dist/hook.js.map +1 -1
- package/dist/inline-atom-background.d.ts +9 -0
- package/dist/inline-atom-background.js +68 -0
- package/dist/inline-atom-background.js.map +1 -0
- package/dist/inline-atom-hook.d.ts +5 -2
- package/dist/inline-atom-hook.js +98 -2
- package/dist/inline-atom-hook.js.map +1 -1
- package/dist/inline-atom-worker.d.ts +2 -0
- package/dist/inline-atom-worker.js +70 -0
- package/dist/inline-atom-worker.js.map +1 -0
- package/dist/reminder-injector.d.ts +3 -0
- package/dist/reminder-injector.js +212 -0
- package/dist/reminder-injector.js.map +1 -0
- package/package.json +1 -1
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 =
|
|
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):
|
|
1244
|
-
//
|
|
1245
|
-
//
|
|
1246
|
-
|
|
1247
|
-
|
|
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
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
`[${
|
|
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 =
|
|
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
|
-
|
|
1531
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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');
|