greprag 5.49.6 → 5.49.9

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.
Files changed (51) hide show
  1. package/dist/commands/collision-reminder.d.ts +14 -0
  2. package/dist/commands/collision-reminder.js +40 -0
  3. package/dist/commands/collision-reminder.js.map +1 -0
  4. package/dist/commands/coordinate-gate.d.ts +12 -6
  5. package/dist/commands/coordinate-gate.js +24 -10
  6. package/dist/commands/coordinate-gate.js.map +1 -1
  7. package/dist/commands/corpus/client.d.ts +5 -1
  8. package/dist/commands/corpus/client.js +10 -3
  9. package/dist/commands/corpus/client.js.map +1 -1
  10. package/dist/commands/corpus/index.js +21 -1
  11. package/dist/commands/corpus/index.js.map +1 -1
  12. package/dist/commands/corpus/manage.js +31 -5
  13. package/dist/commands/corpus/manage.js.map +1 -1
  14. package/dist/commands/corpus/search.js +13 -6
  15. package/dist/commands/corpus/search.js.map +1 -1
  16. package/dist/commands/corpus/status.js +2 -1
  17. package/dist/commands/corpus/status.js.map +1 -1
  18. package/dist/commands/corpus/tags.d.ts +17 -0
  19. package/dist/commands/corpus/tags.js +91 -0
  20. package/dist/commands/corpus/tags.js.map +1 -0
  21. package/dist/commands/corpus-reminder.d.ts +15 -0
  22. package/dist/commands/corpus-reminder.js +34 -0
  23. package/dist/commands/corpus-reminder.js.map +1 -0
  24. package/dist/commands/friction-reminder.d.ts +22 -44
  25. package/dist/commands/friction-reminder.js +45 -63
  26. package/dist/commands/friction-reminder.js.map +1 -1
  27. package/dist/commands/init.js +7 -14
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/load-primer-reminder.d.ts +28 -0
  30. package/dist/commands/load-primer-reminder.js +51 -0
  31. package/dist/commands/load-primer-reminder.js.map +1 -0
  32. package/dist/commands/load.d.ts +15 -0
  33. package/dist/commands/load.js +112 -0
  34. package/dist/commands/load.js.map +1 -0
  35. package/dist/commands/memory-reflex.d.ts +17 -112
  36. package/dist/commands/memory-reflex.js +24 -233
  37. package/dist/commands/memory-reflex.js.map +1 -1
  38. package/dist/commands/reminder-registry.d.ts +12 -1
  39. package/dist/commands/reminder-registry.js +48 -4
  40. package/dist/commands/reminder-registry.js.map +1 -1
  41. package/dist/commands/reminder-types.d.ts +33 -0
  42. package/dist/commands/version-reminder.d.ts +15 -0
  43. package/dist/commands/version-reminder.js +34 -0
  44. package/dist/commands/version-reminder.js.map +1 -0
  45. package/dist/hook.js +134 -274
  46. package/dist/hook.js.map +1 -1
  47. package/dist/index.js +6 -0
  48. package/dist/index.js.map +1 -1
  49. package/package.json +1 -1
  50. package/skill/templates/chip-leader.md +188 -0
  51. package/skill/templates/chip-spawn.md +4 -4
package/dist/hook.js CHANGED
@@ -78,7 +78,6 @@ const guard_1 = require("./guard");
78
78
  const reminder_registry_1 = require("./commands/reminder-registry");
79
79
  const inbox_primer_reminder_1 = require("./commands/inbox-primer-reminder");
80
80
  const friction_reminder_1 = require("./commands/friction-reminder");
81
- const memory_reflex_1 = require("./commands/memory-reflex");
82
81
  const worktree_state_1 = require("./worktree-state");
83
82
  // Ingress-trigger bridge, Adapter A (LOCAL/state half) — the Stop hook computes
84
83
  // the deterministic state values (stress / turnCount / keyword-seen) from the
@@ -786,37 +785,7 @@ async function store(input, source = 'claude-code') {
786
785
  if (source === 'codex') {
787
786
  turn.toolCalls.push(...(0, codex_hook_events_1.readCodexSubagentToolCalls)(input));
788
787
  }
789
- // Memory-reflex EFFICACY capture (adr/memory-reflex.md). If a memory injection was surfaced
790
- // this turn: (1) score the FREE overlap proxy locally → bump the `used` numerator, and (2)
791
- // carry the injection onto the turn POST so the server-side Flash-Lite judge (Chip A) can
792
- // score it accurately. Consume-once: the stash is always cleared, so a turn with no injection
793
- // never reuses a stale sample. adr: adr/memory-reflex.md
794
- let memoryInjectionForStore = null;
795
- try {
796
- const effShort = (0, session_id_1.truncateSessionId)(input.session_id);
797
- if (effShort) {
798
- const pending = consumeMemoryInjection(effShort);
799
- if (pending) {
800
- memoryInjectionForStore = pending.hitText;
801
- if ((0, memory_reflex_1.injectionWasUsed)(pending.hitText, turn.userPrompt, turn.agentResponse)) {
802
- recordMemoryUsed(pending.projectId);
803
- }
804
- }
805
- }
806
- }
807
- catch { /* efficacy capture is best-effort — never block the turn */ }
808
- // Self-report scan (P1b, adr/memory-reflex.md). If the agent emitted a [[recall: X]] marker
809
- // (taught by the memory announce) it's flagging its OWN context gap → stash the topic so the
810
- // NEXT turn's notify pulls + injects it (the self-report trigger). Best-effort.
811
- try {
812
- const srShort = (0, session_id_1.truncateSessionId)(input.session_id);
813
- if (srShort && anchor.projectId) {
814
- const topic = (0, memory_reflex_1.extractRecallMarker)(turn.agentResponse);
815
- if (topic)
816
- stashSelfReport(srShort, anchor.projectId, topic);
817
- }
818
- }
819
- catch { /* best-effort — never block the turn */ }
788
+ // (Memory-reflex efficacy capture removed 2026-06-22 with the auto-inject it scored.)
820
789
  // Empty turn — nothing to capture.
821
790
  if (!turn.userPrompt && !turn.agentResponse && turn.toolCalls.length === 0)
822
791
  return;
@@ -853,12 +822,6 @@ async function store(input, source = 'claude-code') {
853
822
  status: turn.status,
854
823
  userPrompt,
855
824
  agentResponse,
856
- // The memory injection surfaced this turn (when any) — the SEAM the Flash-Lite efficacy
857
- // judge (Chip A) consumes server-side at ingest to score used÷injected. Scrubbed + capped
858
- // like the other text fields. Omitted entirely on the common no-injection turn.
859
- ...(memoryInjectionForStore
860
- ? { memoryInjection: capField((0, secret_scrubber_1.scrubString)(memoryInjectionForStore, redaction)) }
861
- : {}),
862
825
  toolCalls,
863
826
  filesTouched: turn.filesTouched,
864
827
  artifacts: artifactRefs,
@@ -938,6 +901,24 @@ async function fetchFrontDeskUnread(apiUrl, apiKey) {
938
901
  return 0;
939
902
  }
940
903
  }
904
+ /** Fetch the names of api-docs-tagged corpus stores (the corpus-announce signal).
905
+ * Lists `/v1/stores?tag=api-docs` and returns the store names so the announce can
906
+ * name the on-tap reference banks. Best-effort: [] on any error → the announce
907
+ * stays silent. adr: adr/corpus-tags.md */
908
+ async function fetchCorpusApiDocs(apiUrl, apiKey) {
909
+ try {
910
+ const res = await fetch(`${apiUrl}/v1/stores?tag=api-docs`, {
911
+ headers: { 'Authorization': `Bearer ${apiKey}` },
912
+ });
913
+ if (!res.ok)
914
+ return [];
915
+ const data = await res.json();
916
+ return (data.stores || []).map(s => s.name || '').filter(Boolean);
917
+ }
918
+ catch {
919
+ return [];
920
+ }
921
+ }
941
922
  /** Fetch this session's OWN unread count — messages addressed to `to_session_id =
942
923
  * <short>` (mail a peer or the operator sent to THIS session). Safe + non-eavesdrop:
943
924
  * reads only this session's mail, NOT the project-wide count the v5.6.1 removal retired
@@ -957,44 +938,6 @@ async function fetchSessionUnread(apiUrl, apiKey, short) {
957
938
  return 0;
958
939
  }
959
940
  }
960
- /** Fetch top episodic-memory hits for a query (the memory-reflex inline path), project-
961
- * scoped, with a HARD timeout so a slow search never blocks the turn. POSTs the same
962
- * `/v1/memory/query` the CLI `memory search` uses, with the de-pollution `session-turn`
963
- * provenance filter. Returns [] on any error/abort → the reflex stays silent. The
964
- * confidence gate + framing live in buildMemoryInjection (commands/memory-reflex.ts).
965
- * adr: adr/memory-reflex.md */
966
- async function fetchMemoryHits(apiUrl, apiKey, projectId, query, limit = 3, timeoutMs = 2000) {
967
- const ctl = new AbortController();
968
- const timer = setTimeout(() => ctl.abort(), timeoutMs);
969
- try {
970
- const res = await fetch(`${apiUrl}/v1/memory/query`, {
971
- method: 'POST',
972
- headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
973
- body: JSON.stringify({ query, limit, projectId, filters: { provenance: 'session-turn' } }),
974
- signal: ctl.signal,
975
- });
976
- if (!res.ok)
977
- return { hits: [], quiet: false };
978
- const data = await res.json();
979
- // The server's Flash-Lite efficacy verdict rides the same response (adr/memory-reflex.md).
980
- const quiet = data.memoryReflexQuiet === true;
981
- if (!data.ok || !Array.isArray(data.nodes))
982
- return { hits: [], quiet };
983
- return {
984
- hits: data.nodes.map(n => ({
985
- content: n.content, score: n.score, confidence: n.confidence ?? null,
986
- shape: n.shape ?? null, createdAt: n.createdAt ?? null, projectName: n.projectName ?? null,
987
- })),
988
- quiet,
989
- };
990
- }
991
- catch {
992
- return { hits: [], quiet: false };
993
- }
994
- finally {
995
- clearTimeout(timer);
996
- }
997
- }
998
941
  /** Identity-drift warning rate limiter.
999
942
  *
1000
943
  * The drift warning ("stored ID ≠ git-derived ID, run `greprag doctor`") used
@@ -1069,6 +1012,78 @@ function writeRecapOutput(text, mode) {
1069
1012
  }
1070
1013
  process.stdout.write(text);
1071
1014
  }
1015
+ /** This CLI's installed version (from the bundled package.json). Null if unreadable. */
1016
+ function installedVersion() {
1017
+ try {
1018
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
1019
+ return typeof pkg.version === 'string' ? pkg.version : null;
1020
+ }
1021
+ catch {
1022
+ return null;
1023
+ }
1024
+ }
1025
+ /** Is semver `b` strictly greater than `a`? Plain numeric major.minor.patch compare
1026
+ * (greprag never ships pre-release tags). Non-numeric segments collapse to 0. */
1027
+ function isNewer(a, b) {
1028
+ const pa = a.split('.').map(n => parseInt(n, 10) || 0);
1029
+ const pb = b.split('.').map(n => parseInt(n, 10) || 0);
1030
+ for (let i = 0; i < 3; i++) {
1031
+ if ((pb[i] || 0) > (pa[i] || 0))
1032
+ return true;
1033
+ if ((pb[i] || 0) < (pa[i] || 0))
1034
+ return false;
1035
+ }
1036
+ return false;
1037
+ }
1038
+ const VERSION_CHECK_TTL_MS = 24 * 60 * 60 * 1000; // once a day
1039
+ function versionCheckPath() {
1040
+ const home = process.env.HOME || process.env.USERPROFILE || '';
1041
+ return path.join(home, '.greprag', 'state', 'version-check.json');
1042
+ }
1043
+ /** The Deficiency-gated upgrade signal (version-reminder.ts). Returns {current, latest}
1044
+ * when the installed CLI is behind the latest published version, else null. The npm-registry
1045
+ * read is cached daily (a slow/offline check never blocks SessionStart — stale cache or no
1046
+ * cache → no announce, fail-quiet). */
1047
+ async function checkForUpdate() {
1048
+ const current = installedVersion();
1049
+ if (!current)
1050
+ return null;
1051
+ let latest = null;
1052
+ try {
1053
+ const raw = fs.readFileSync(versionCheckPath(), 'utf-8');
1054
+ const c = JSON.parse(raw);
1055
+ const age = c.checkedAt ? Date.now() - Date.parse(c.checkedAt) : Infinity;
1056
+ if (typeof c.latest === 'string' && Number.isFinite(age) && age < VERSION_CHECK_TTL_MS) {
1057
+ latest = c.latest; // fresh cache hit
1058
+ }
1059
+ }
1060
+ catch { /* no/bad cache — refetch */ }
1061
+ if (!latest) {
1062
+ try {
1063
+ const ctl = new AbortController();
1064
+ const timer = setTimeout(() => ctl.abort(), 1500);
1065
+ const res = await fetch('https://registry.npmjs.org/greprag/latest', { signal: ctl.signal });
1066
+ clearTimeout(timer);
1067
+ if (res.ok) {
1068
+ const data = await res.json();
1069
+ if (typeof data.version === 'string') {
1070
+ latest = data.version;
1071
+ try {
1072
+ const file = versionCheckPath();
1073
+ if (!fs.existsSync(path.dirname(file)))
1074
+ fs.mkdirSync(path.dirname(file), { recursive: true });
1075
+ fs.writeFileSync(file, JSON.stringify({ latest, checkedAt: new Date().toISOString() }) + '\n');
1076
+ }
1077
+ catch { /* cache write best-effort */ }
1078
+ }
1079
+ }
1080
+ }
1081
+ catch { /* offline / slow → no announce this session */ }
1082
+ }
1083
+ if (latest && isNewer(current, latest))
1084
+ return { current, latest };
1085
+ return null;
1086
+ }
1072
1087
  /** SessionStart — fetch recent episodic activity for this project and print
1073
1088
  * to stdout. Claude Code injects the printed text as session context. Codex
1074
1089
  * uses the same content via `hookSpecificOutput.additionalContext`. Fires
@@ -1177,12 +1192,19 @@ async function recap(input, mode = 'plain') {
1177
1192
  // by the front-desk module's announce. NOT the v5.6.1 eavesdrop vector — see
1178
1193
  // fetchFrontDeskUnread. adr: adr/address-grammar.md (2026-06-10), docs/front-desk-switchboard.md §5
1179
1194
  const frontDeskUnread = await fetchFrontDeskUnread(cfg.apiUrl, cfg.apiKey);
1195
+ // Corpus-announce signal — the names of the api-docs-tagged reference banks on
1196
+ // tap, so the corpus module can tell the agent to search them before answering
1197
+ // from training data. Best-effort; empty → the announce stays silent.
1198
+ const corpusApiDocs = await fetchCorpusApiDocs(cfg.apiUrl, cfg.apiKey);
1180
1199
  // Assistant doctrine auto-load — fires ONLY for the flagged assistant project
1181
1200
  // (isAssistantProject), so a normal project sees zero change. The hook owns the
1182
1201
  // doctrine-file read; the assistant-doctrine module routes the text. adr: adr/assistant-role.md
1183
1202
  const assistantDoctrine = (0, project_anchor_1.isAssistantProject)(anchor)
1184
1203
  ? (0, assistant_doctrine_1.buildAssistantDoctrineContext)(cwd)
1185
1204
  : null;
1205
+ // Deficiency-gated upgrade announce: daily-cached npm check → tell the user to
1206
+ // upgrade when behind. This is how a release actually propagates to everyone.
1207
+ const updateAvailable = await checkForUpdate();
1186
1208
  // ── THE single SessionStart announce assembly (docs/reminder-interrupt.md §Registry
1187
1209
  // spec) ──────────────────────────────────────────────────────────────────────────────
1188
1210
  // Every agent-facing announce — setup-warning, front-desk, checkpoint, assistant-doctrine,
@@ -1206,6 +1228,8 @@ async function recap(input, mode = 'plain') {
1206
1228
  frontDeskUnread,
1207
1229
  openCheckpoints,
1208
1230
  assistantDoctrine,
1231
+ corpusApiDocs,
1232
+ updateAvailable,
1209
1233
  };
1210
1234
  let announceReg = mechanicKilled() ? reminder_registry_1.REGISTRY.filter(m => m.id !== 'mechanic-friction') : reminder_registry_1.REGISTRY;
1211
1235
  if (!announceShort)
@@ -1379,165 +1403,14 @@ function recordFrictionFire(projectId, tier, turnCount) {
1379
1403
  }
1380
1404
  catch { /* counter is best-effort — never block the turn */ }
1381
1405
  }
1382
- /** Local path for this project's memory-reflex fire-counter — the data the /mechanic
1383
- * excessiveness monitor reads (fires · injected · silent · per-trigger). Beside the
1384
- * friction counter under ~/.greprag/state. */
1385
- function memoryReflexStatsPath(projectId) {
1386
- const home = process.env.HOME || process.env.USERPROFILE || '';
1387
- return path.join(home, '.greprag', 'state', `memory-reflex-${projectId}.json`);
1388
- }
1389
- /** Tally one memory-reflex fire (best-effort). `didInject` splits fire→injected vs silent
1390
- * so the monitor can read the inject/fire ratio (firing without finding = noise). */
1391
- function recordMemoryFire(projectId, trigger, didInject, turnCount) {
1392
- try {
1393
- const file = memoryReflexStatsPath(projectId);
1394
- let prior = null;
1395
- try {
1396
- prior = JSON.parse(fs.readFileSync(file, 'utf-8'));
1397
- }
1398
- catch { /* none yet */ }
1399
- const next = (0, memory_reflex_1.tallyMemoryFire)(prior, trigger, didInject, turnCount, new Date().toISOString());
1400
- const dir = path.dirname(file);
1401
- if (!fs.existsSync(dir))
1402
- fs.mkdirSync(dir, { recursive: true });
1403
- fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
1404
- }
1405
- catch { /* counter is best-effort — never block the turn */ }
1406
- }
1407
- /** Stash for the memory-reflex efficacy capture — the raw hit content injected THIS turn, so
1408
- * the Stop hook can score the response's overlap against it (the `used` numerator). Keyed by
1409
- * session (Stop has the short id); carries projectId so Stop needn't re-derive it. Written at
1410
- * inject-time, consumed + deleted at Stop. adr: adr/memory-reflex.md */
1411
- function memoryReflexPendingPath(short) {
1412
- const home = process.env.HOME || process.env.USERPROFILE || '';
1413
- return path.join(home, '.greprag', 'state', `memory-reflex-pending-${short}.json`);
1414
- }
1415
- function stashMemoryInjection(short, projectId, hitText, turnCount) {
1416
- try {
1417
- const file = memoryReflexPendingPath(short);
1418
- const dir = path.dirname(file);
1419
- if (!fs.existsSync(dir))
1420
- fs.mkdirSync(dir, { recursive: true });
1421
- fs.writeFileSync(file, JSON.stringify({ projectId, hitText, turnCount }) + '\n');
1422
- }
1423
- catch { /* best-effort — a missed stash just means no efficacy sample this turn */ }
1424
- }
1425
- /** Read + DELETE the pending injection stash (consume-once, so a turn with no injection never
1426
- * reuses a stale sample). Returns null on the common no-stash path. */
1427
- function consumeMemoryInjection(short) {
1428
- try {
1429
- const file = memoryReflexPendingPath(short);
1430
- const raw = fs.readFileSync(file, 'utf-8');
1431
- try {
1432
- fs.unlinkSync(file);
1433
- }
1434
- catch { /* already gone — fine */ }
1435
- const p = JSON.parse(raw);
1436
- if (!p || typeof p.projectId !== 'string' || typeof p.hitText !== 'string')
1437
- return null;
1438
- return { projectId: p.projectId, hitText: p.hitText };
1439
- }
1440
- catch {
1441
- return null; // no stash this turn (the common case)
1442
- }
1443
- }
1444
- /** Bump the memory-reflex EFFICACY numerator (the response drew on the injection) — best-effort. */
1445
- function recordMemoryUsed(projectId) {
1446
- try {
1447
- const file = memoryReflexStatsPath(projectId);
1448
- let prior = null;
1449
- try {
1450
- prior = JSON.parse(fs.readFileSync(file, 'utf-8'));
1451
- }
1452
- catch { /* none yet */ }
1453
- const next = (0, memory_reflex_1.tallyMemoryUsed)(prior);
1454
- const dir = path.dirname(file);
1455
- if (!fs.existsSync(dir))
1456
- fs.mkdirSync(dir, { recursive: true });
1457
- fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
1458
- }
1459
- catch { /* counter is best-effort */ }
1460
- }
1461
- /** Self-report stash (P1b, adr/memory-reflex.md) — the topic the agent flagged via a
1462
- * `[[recall: X]]` marker LAST turn, so the NEXT turn's notify pulls + injects it. Separate
1463
- * from the efficacy-injection stash. Keyed by session; carries projectId. Written at Stop,
1464
- * consumed-once at the next notify. */
1465
- function selfReportPendingPath(short) {
1466
- const home = process.env.HOME || process.env.USERPROFILE || '';
1467
- return path.join(home, '.greprag', 'state', `memory-reflex-selfreport-${short}.json`);
1468
- }
1469
- function stashSelfReport(short, projectId, topic) {
1470
- try {
1471
- const file = selfReportPendingPath(short);
1472
- const dir = path.dirname(file);
1473
- if (!fs.existsSync(dir))
1474
- fs.mkdirSync(dir, { recursive: true });
1475
- fs.writeFileSync(file, JSON.stringify({ projectId, topic }) + '\n');
1476
- }
1477
- catch { /* best-effort — a missed self-report just means no pull this round */ }
1478
- }
1479
- /** Read + DELETE the self-report stash (consume-once). Null on the common no-marker path. */
1480
- function consumeSelfReport(short) {
1481
- try {
1482
- const file = selfReportPendingPath(short);
1483
- const raw = fs.readFileSync(file, 'utf-8');
1484
- try {
1485
- fs.unlinkSync(file);
1486
- }
1487
- catch { /* already gone — fine */ }
1488
- const p = JSON.parse(raw);
1489
- if (!p || typeof p.projectId !== 'string' || typeof p.topic !== 'string' || !p.topic)
1490
- return null;
1491
- return { projectId: p.projectId, topic: p.topic };
1492
- }
1493
- catch {
1494
- return null;
1495
- }
1496
- }
1497
- /** Local cache of the SERVER's auto-quiet verdict for this project (the
1498
- * mechanicKilled() analog for the memory-reflex). The Flash-Lite efficacy judge
1499
- * decides the threshold server-side and ships the boolean on the /v1/memory/query
1500
- * response; the hook caches it here and `memoryReflexQuieted` honors it with a
1501
- * TTL (quietCacheSaysSilent). Beside the fire-counter under ~/.greprag/state. */
1502
- function memoryReflexQuietPath(projectId) {
1503
- const home = process.env.HOME || process.env.USERPROFILE || '';
1504
- return path.join(home, '.greprag', 'state', `memory-reflex-quiet-${projectId}.json`);
1505
- }
1506
- /** Has the server's efficacy judge QUIETED this project's memory-reflex (low
1507
- * used÷injected over a meaningful sample)? Reads the local cache; honors it only
1508
- * while fresh (TTL) so a recovered reflex re-probes instead of staying silenced.
1509
- * Fail-quiet: any error reads as NOT quieted (the reflex keeps working) —
1510
- * mirrors mechanicKilled()'s fail-open posture. */
1511
- function memoryReflexQuieted(projectId) {
1512
- try {
1513
- const raw = fs.readFileSync(memoryReflexQuietPath(projectId), 'utf-8');
1514
- return (0, memory_reflex_1.quietCacheSaysSilent)(JSON.parse(raw), Date.now());
1515
- }
1516
- catch {
1517
- return false;
1518
- }
1519
- }
1520
- /** Cache the server's auto-quiet verdict from a /v1/memory/query response — written
1521
- * whenever the reflex actually probes (fired the search). Best-effort. */
1522
- function cacheMemoryReflexQuiet(projectId, quiet) {
1523
- try {
1524
- const file = memoryReflexQuietPath(projectId);
1525
- const dir = path.dirname(file);
1526
- if (!fs.existsSync(dir))
1527
- fs.mkdirSync(dir, { recursive: true });
1528
- const payload = { quiet, cachedAt: new Date().toISOString() };
1529
- fs.writeFileSync(file, JSON.stringify(payload) + '\n');
1530
- }
1531
- catch { /* best-effort — a missed cache just means the reflex re-probes next turn */ }
1532
- }
1533
1406
  /** The reminder-interrupt registry CONTAINER call (docs/reminder-interrupt.md §Registry
1534
1407
  * spec) — invoked from the proven-registered `notify` hook (claude-code path). Assembles
1535
1408
  * the live env (the ONLY i/o: armed via isLocallyArmed, this session's own unread, the
1536
1409
  * Stop-stashed stress/turnCount, the front-desk envelope notice) and fires every module
1537
1410
  * whose detector reports deficient: watcher-arm (unarmed → nag if a peer messaged you, else
1538
- * nudge; silent when armed), the stress-gated mechanic-friction, front-desk (a soft
1539
- * "you've got mail" when a new envelope landed), and memory-reflex (auto-surfaced episodic
1540
- * memory when THIS prompt signals recall an inline, time-capped, confidence-gated search).
1411
+ * nudge; silent when armed), the stress-gated mechanic-friction, and front-desk (a soft
1412
+ * "you've got mail" when a new envelope landed). The memory PRIMER announces at SessionStart;
1413
+ * the auto-inject was dropped (knowledge injection, not a reminder docs/reminder-interrupt.md).
1541
1414
  * The announce-only modules (setup-warning / checkpoint / assistant-doctrine) stay silent per
1542
1415
  * turn — their env signals are SessionStart-only. Stacking; emitted once. Fail-open — any miss
1543
1416
  * → silent. The Mechanic kill switch drops ONLY the mechanic module; the rest keep working. */
@@ -1564,48 +1437,22 @@ async function injectReminders(input, cfg, short) {
1564
1437
  // null when nothing new. This is the network call the retired `mail` hook used to make;
1565
1438
  // it moves here so the whole announce/reminder surface rides ONE detector loop.
1566
1439
  const frontDeskNotice = await (0, front_desk_mail_1.buildFrontDeskNotice)(cfg.apiUrl, cfg.apiKey);
1567
- // Memory-reflex (keyword layer) recall intent in THIS prompt fires an inline,
1568
- // time-capped, project-scoped search; a hit above the confidence gate is surfaced
1569
- // inline (top 1-2, capped) so the agent sees it AS it forms its reply. A miss stays
1570
- // silent (the search result is its own precision filter no banner-blindness). Every
1571
- // fire is tallied for the /mechanic excessiveness monitor. Gated on recall-intent, so
1572
- // most turns skip the search entirely. docs/reminder-interrupt.md (memory-reflex).
1573
- let memoryHit = null;
1574
- const prompt = typeof input.prompt === 'string' ? input.prompt : '';
1575
- // Pick the memory query + trigger (the hybrid): a self-report marker the agent emitted LAST
1576
- // turn ([[recall: X]], stashed at Stop) takes precedence over recall-intent in THIS prompt —
1577
- // the agent explicitly flagged its own gap. One injection per turn.
1578
- let memQuery = null;
1579
- let memTrigger = 'keyword';
1580
- const selfReport = anchor.projectId ? consumeSelfReport(short) : null;
1581
- if (selfReport) {
1582
- memQuery = selfReport.topic;
1583
- memTrigger = 'selfReport';
1584
- }
1585
- else if ((0, memory_reflex_1.hasRecallIntent)(prompt)) {
1586
- memQuery = prompt.slice(0, 500);
1587
- memTrigger = 'keyword';
1588
- }
1589
- // Auto-quiet gate (adr/memory-reflex.md): if the server's Flash-Lite efficacy judge has
1590
- // QUIETED this project's reflex (low used÷injected over a meaningful sample), skip the search
1591
- // entirely — the cached verdict expires on a TTL so a recovered reflex re-probes. mechanicKilled() analog.
1592
- if (anchor.projectId && memQuery && !memoryReflexQuieted(anchor.projectId)) {
1593
- const { hits, quiet } = await fetchMemoryHits(cfg.apiUrl, cfg.apiKey, anchor.projectId, memQuery);
1594
- cacheMemoryReflexQuiet(anchor.projectId, quiet); // refresh the verdict from this probe
1595
- memoryHit = (0, memory_reflex_1.buildMemoryInjection)(hits);
1596
- recordMemoryFire(anchor.projectId, memTrigger, !!memoryHit, turnCount);
1597
- // Stash what was injected so the Stop hook can score whether the response used it
1598
- // (the efficacy numerator). Only when something was actually surfaced.
1599
- if (memoryHit)
1600
- stashMemoryInjection(short, anchor.projectId, (0, memory_reflex_1.gatedHitText)(hits), turnCount);
1601
- }
1440
+ // NOTE: the per-turn memory AUTO-INJECT (recall-intent search surface a hit) was
1441
+ // DROPPED 2026-06-22. It was knowledge-injection, NOT a behavioral reminder a
1442
+ // different (undeveloped) system that was jammed into this registry for lack of a slot.
1443
+ // It also collided with the agent's own better-targeted `greprag memory search` (taught
1444
+ // by the memory primer). The memory PRIMER (the announce) stays; only the auto-inject is
1445
+ // gone. docs/reminder-interrupt.md (the landed map: reminder ≠ knowledge injection).
1602
1446
  const env = {
1603
1447
  short, turnCount, stress, armed, sessionUnread,
1604
1448
  ownerPid, alias: (0, session_id_1.readIdentityAlias)(), assistant,
1605
- frontDeskNotice, memoryHit,
1449
+ frontDeskNotice,
1606
1450
  };
1607
- // Kill switch drops ONLY the mechanic module watcher-arm keeps working.
1608
- const reg = mechanicKilled() ? reminder_registry_1.REGISTRY.filter(m => m.id !== 'mechanic-friction') : reminder_registry_1.REGISTRY;
1451
+ // UserPromptSubmit evaluates the prompt-source modules only (command-source
1452
+ // modules the collision Match run at PreToolUse). Kill switch drops ONLY
1453
+ // the mechanic module — watcher-arm keeps working.
1454
+ const base = (0, reminder_registry_1.promptModules)();
1455
+ const reg = mechanicKilled() ? base.filter(m => m.id !== 'mechanic-friction') : base;
1609
1456
  const fired = (0, reminder_registry_1.collectReminders)(env, reg);
1610
1457
  if (!fired.length)
1611
1458
  return;
@@ -1785,7 +1632,7 @@ function validateChip(title, prompt) {
1785
1632
  * adr/spawn-task-hook-mode.md 2026-05-27 entry — Tier 2 augmentation
1786
1633
  * attempt). `permissionDecision: deny` IS honored, so the validator
1787
1634
  * catches missing Block 1 / Block 2 markers; the agent re-composes the
1788
- * call with the templates from `~/.claude/docs/chip-spawn.md`. */
1635
+ * call with the templates from `greprag load chip-spawn`. */
1789
1636
  function handlePreSpawnCheck(input) {
1790
1637
  if (input.tool_name !== 'mcp__ccd_session__spawn_task')
1791
1638
  return;
@@ -1796,7 +1643,7 @@ function handlePreSpawnCheck(input) {
1796
1643
  return;
1797
1644
  const reason = `Chip prompt rejected by greprag PreToolUse validator. Fix the following and re-call spawn_task:\n - ` +
1798
1645
  violations.join('\n - ') +
1799
- `\n\nFull conventions: ~/.claude/docs/chip-spawn.md.`;
1646
+ `\n\nFull conventions: run \`greprag load chip-spawn\`.`;
1800
1647
  process.stdout.write(JSON.stringify({
1801
1648
  hookSpecificOutput: {
1802
1649
  hookEventName: 'PreToolUse',
@@ -1867,7 +1714,8 @@ async function coordinateGate(input) {
1867
1714
  if (!short)
1868
1715
  return; // no session id → silent
1869
1716
  // Local + cheap: classify BEFORE any network so non-risky calls cost nothing.
1870
- const trigger = (0, coordinate_gate_1.triggerFromPreToolUse)(input.tool_name || '', (input.tool_input || {}));
1717
+ const toolInput = (input.tool_input || {});
1718
+ const trigger = (0, coordinate_gate_1.triggerFromPreToolUse)(input.tool_name || '', toolInput);
1871
1719
  if (!trigger)
1872
1720
  return; // not a risky action → silent
1873
1721
  let anchor;
@@ -1879,7 +1727,8 @@ async function coordinateGate(input) {
1879
1727
  }
1880
1728
  if (!anchor.projectId)
1881
1729
  return; // unanchored → nothing to match
1882
- const directive = await (0, coordinate_gate_1.runCoordinateGate)(trigger, {
1730
+ // I/O stays in the hook: fresh peer read off the hot path → env.collisionPeers.
1731
+ const peers = await (0, coordinate_gate_1.resolveCollisionPeers)({
1883
1732
  short,
1884
1733
  projectId: anchor.projectId,
1885
1734
  projectName: anchor.projectName,
@@ -1887,15 +1736,26 @@ async function coordinateGate(input) {
1887
1736
  apiKey: cfg.apiKey,
1888
1737
  alias: (0, session_id_1.readIdentityAlias)(),
1889
1738
  });
1890
- if (!directive)
1739
+ if (peers.length === 0)
1891
1740
  return; // no live peer in this repo → proceed
1741
+ // Route through the registry: build the command-read env, run the command-source
1742
+ // modules (the collision Match). The standalone gate is now a registry citizen.
1743
+ const env = {
1744
+ short, turnCount: 0, stress: 0, armed: false, sessionUnread: 0,
1745
+ alias: (0, session_id_1.readIdentityAlias)(),
1746
+ toolCommand: typeof toolInput.command === 'string' ? toolInput.command : '',
1747
+ collisionPeers: peers,
1748
+ };
1749
+ const fired = (0, reminder_registry_1.collectReminders)(env, (0, reminder_registry_1.commandModules)());
1750
+ if (!fired.length)
1751
+ return;
1892
1752
  // EFFECT: inject as additionalContext — advisory heads-up, NO permission pause.
1893
1753
  // `ask` was too intrusive (prompted on every git op while peers were live); inject
1894
1754
  // keeps the agent aware without a manual-allow. adr: adr/monitor-resilience.md
1895
1755
  process.stdout.write(JSON.stringify({
1896
1756
  hookSpecificOutput: {
1897
1757
  hookEventName: input.hook_event_name || 'PreToolUse',
1898
- additionalContext: directive,
1758
+ additionalContext: fired.map(f => f.line).join('\n\n'),
1899
1759
  },
1900
1760
  }) + '\n');
1901
1761
  }