greprag 5.49.8 → 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.
- package/dist/commands/collision-reminder.d.ts +14 -0
- package/dist/commands/collision-reminder.js +40 -0
- package/dist/commands/collision-reminder.js.map +1 -0
- package/dist/commands/coordinate-gate.d.ts +12 -6
- package/dist/commands/coordinate-gate.js +24 -10
- package/dist/commands/coordinate-gate.js.map +1 -1
- package/dist/commands/friction-reminder.d.ts +22 -44
- package/dist/commands/friction-reminder.js +45 -63
- package/dist/commands/friction-reminder.js.map +1 -1
- package/dist/commands/init.js +7 -14
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/load-primer-reminder.d.ts +28 -0
- package/dist/commands/load-primer-reminder.js +51 -0
- package/dist/commands/load-primer-reminder.js.map +1 -0
- package/dist/commands/load.d.ts +15 -0
- package/dist/commands/load.js +112 -0
- package/dist/commands/load.js.map +1 -0
- package/dist/commands/memory-reflex.d.ts +17 -104
- package/dist/commands/memory-reflex.js +23 -215
- package/dist/commands/memory-reflex.js.map +1 -1
- package/dist/commands/reminder-registry.d.ts +12 -1
- package/dist/commands/reminder-registry.js +46 -4
- package/dist/commands/reminder-registry.js.map +1 -1
- package/dist/commands/reminder-types.d.ts +28 -0
- package/dist/commands/version-reminder.d.ts +15 -0
- package/dist/commands/version-reminder.js +34 -0
- package/dist/commands/version-reminder.js.map +1 -0
- package/dist/hook.js +111 -219
- package/dist/hook.js.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skill/templates/chip-leader.md +188 -0
- 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,25 +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
|
|
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 */ }
|
|
788
|
+
// (Memory-reflex efficacy capture removed 2026-06-22 with the auto-inject it scored.)
|
|
808
789
|
// Empty turn — nothing to capture.
|
|
809
790
|
if (!turn.userPrompt && !turn.agentResponse && turn.toolCalls.length === 0)
|
|
810
791
|
return;
|
|
@@ -841,12 +822,6 @@ async function store(input, source = 'claude-code') {
|
|
|
841
822
|
status: turn.status,
|
|
842
823
|
userPrompt,
|
|
843
824
|
agentResponse,
|
|
844
|
-
// The memory injection surfaced this turn (when any) — the SEAM the Flash-Lite efficacy
|
|
845
|
-
// judge (Chip A) consumes server-side at ingest to score used÷injected. Scrubbed + capped
|
|
846
|
-
// like the other text fields. Omitted entirely on the common no-injection turn.
|
|
847
|
-
...(memoryInjectionForStore
|
|
848
|
-
? { memoryInjection: capField((0, secret_scrubber_1.scrubString)(memoryInjectionForStore, redaction)) }
|
|
849
|
-
: {}),
|
|
850
825
|
toolCalls,
|
|
851
826
|
filesTouched: turn.filesTouched,
|
|
852
827
|
artifacts: artifactRefs,
|
|
@@ -963,44 +938,6 @@ async function fetchSessionUnread(apiUrl, apiKey, short) {
|
|
|
963
938
|
return 0;
|
|
964
939
|
}
|
|
965
940
|
}
|
|
966
|
-
/** Fetch top episodic-memory hits for a query (the memory-reflex inline path), project-
|
|
967
|
-
* scoped, with a HARD timeout so a slow search never blocks the turn. POSTs the same
|
|
968
|
-
* `/v1/memory/query` the CLI `memory search` uses, with the de-pollution `session-turn`
|
|
969
|
-
* provenance filter. Returns [] on any error/abort → the reflex stays silent. The
|
|
970
|
-
* confidence gate + framing live in buildMemoryInjection (commands/memory-reflex.ts).
|
|
971
|
-
* adr: adr/memory-reflex.md */
|
|
972
|
-
async function fetchMemoryHits(apiUrl, apiKey, projectId, query, limit = 3, timeoutMs = 2000) {
|
|
973
|
-
const ctl = new AbortController();
|
|
974
|
-
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
975
|
-
try {
|
|
976
|
-
const res = await fetch(`${apiUrl}/v1/memory/query`, {
|
|
977
|
-
method: 'POST',
|
|
978
|
-
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
979
|
-
body: JSON.stringify({ query, limit, projectId, filters: { provenance: 'session-turn' } }),
|
|
980
|
-
signal: ctl.signal,
|
|
981
|
-
});
|
|
982
|
-
if (!res.ok)
|
|
983
|
-
return { hits: [], quiet: false };
|
|
984
|
-
const data = await res.json();
|
|
985
|
-
// The server's Flash-Lite efficacy verdict rides the same response (adr/memory-reflex.md).
|
|
986
|
-
const quiet = data.memoryReflexQuiet === true;
|
|
987
|
-
if (!data.ok || !Array.isArray(data.nodes))
|
|
988
|
-
return { hits: [], quiet };
|
|
989
|
-
return {
|
|
990
|
-
hits: data.nodes.map(n => ({
|
|
991
|
-
content: n.content, score: n.score, confidence: n.confidence ?? null,
|
|
992
|
-
shape: n.shape ?? null, createdAt: n.createdAt ?? null, projectName: n.projectName ?? null,
|
|
993
|
-
})),
|
|
994
|
-
quiet,
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
catch {
|
|
998
|
-
return { hits: [], quiet: false };
|
|
999
|
-
}
|
|
1000
|
-
finally {
|
|
1001
|
-
clearTimeout(timer);
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
941
|
/** Identity-drift warning rate limiter.
|
|
1005
942
|
*
|
|
1006
943
|
* The drift warning ("stored ID ≠ git-derived ID, run `greprag doctor`") used
|
|
@@ -1075,6 +1012,78 @@ function writeRecapOutput(text, mode) {
|
|
|
1075
1012
|
}
|
|
1076
1013
|
process.stdout.write(text);
|
|
1077
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
|
+
}
|
|
1078
1087
|
/** SessionStart — fetch recent episodic activity for this project and print
|
|
1079
1088
|
* to stdout. Claude Code injects the printed text as session context. Codex
|
|
1080
1089
|
* uses the same content via `hookSpecificOutput.additionalContext`. Fires
|
|
@@ -1193,6 +1202,9 @@ async function recap(input, mode = 'plain') {
|
|
|
1193
1202
|
const assistantDoctrine = (0, project_anchor_1.isAssistantProject)(anchor)
|
|
1194
1203
|
? (0, assistant_doctrine_1.buildAssistantDoctrineContext)(cwd)
|
|
1195
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();
|
|
1196
1208
|
// ── THE single SessionStart announce assembly (docs/reminder-interrupt.md §Registry
|
|
1197
1209
|
// spec) ──────────────────────────────────────────────────────────────────────────────
|
|
1198
1210
|
// Every agent-facing announce — setup-warning, front-desk, checkpoint, assistant-doctrine,
|
|
@@ -1217,6 +1229,7 @@ async function recap(input, mode = 'plain') {
|
|
|
1217
1229
|
openCheckpoints,
|
|
1218
1230
|
assistantDoctrine,
|
|
1219
1231
|
corpusApiDocs,
|
|
1232
|
+
updateAvailable,
|
|
1220
1233
|
};
|
|
1221
1234
|
let announceReg = mechanicKilled() ? reminder_registry_1.REGISTRY.filter(m => m.id !== 'mechanic-friction') : reminder_registry_1.REGISTRY;
|
|
1222
1235
|
if (!announceShort)
|
|
@@ -1390,129 +1403,14 @@ function recordFrictionFire(projectId, tier, turnCount) {
|
|
|
1390
1403
|
}
|
|
1391
1404
|
catch { /* counter is best-effort — never block the turn */ }
|
|
1392
1405
|
}
|
|
1393
|
-
/** Local path for this project's memory-reflex fire-counter — the data the /mechanic
|
|
1394
|
-
* excessiveness monitor reads (fires · injected · silent · per-trigger). Beside the
|
|
1395
|
-
* friction counter under ~/.greprag/state. */
|
|
1396
|
-
function memoryReflexStatsPath(projectId) {
|
|
1397
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
1398
|
-
return path.join(home, '.greprag', 'state', `memory-reflex-${projectId}.json`);
|
|
1399
|
-
}
|
|
1400
|
-
/** Tally one memory-reflex fire (best-effort). `didInject` splits fire→injected vs silent
|
|
1401
|
-
* so the monitor can read the inject/fire ratio (firing without finding = noise). */
|
|
1402
|
-
function recordMemoryFire(projectId, trigger, didInject, turnCount) {
|
|
1403
|
-
try {
|
|
1404
|
-
const file = memoryReflexStatsPath(projectId);
|
|
1405
|
-
let prior = null;
|
|
1406
|
-
try {
|
|
1407
|
-
prior = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
1408
|
-
}
|
|
1409
|
-
catch { /* none yet */ }
|
|
1410
|
-
const next = (0, memory_reflex_1.tallyMemoryFire)(prior, trigger, didInject, turnCount, new Date().toISOString());
|
|
1411
|
-
const dir = path.dirname(file);
|
|
1412
|
-
if (!fs.existsSync(dir))
|
|
1413
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1414
|
-
fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
|
|
1415
|
-
}
|
|
1416
|
-
catch { /* counter is best-effort — never block the turn */ }
|
|
1417
|
-
}
|
|
1418
|
-
/** Stash for the memory-reflex efficacy capture — the raw hit content injected THIS turn, so
|
|
1419
|
-
* the Stop hook can score the response's overlap against it (the `used` numerator). Keyed by
|
|
1420
|
-
* session (Stop has the short id); carries projectId so Stop needn't re-derive it. Written at
|
|
1421
|
-
* inject-time, consumed + deleted at Stop. adr: adr/memory-reflex.md */
|
|
1422
|
-
function memoryReflexPendingPath(short) {
|
|
1423
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
1424
|
-
return path.join(home, '.greprag', 'state', `memory-reflex-pending-${short}.json`);
|
|
1425
|
-
}
|
|
1426
|
-
function stashMemoryInjection(short, projectId, hitText, turnCount) {
|
|
1427
|
-
try {
|
|
1428
|
-
const file = memoryReflexPendingPath(short);
|
|
1429
|
-
const dir = path.dirname(file);
|
|
1430
|
-
if (!fs.existsSync(dir))
|
|
1431
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1432
|
-
fs.writeFileSync(file, JSON.stringify({ projectId, hitText, turnCount }) + '\n');
|
|
1433
|
-
}
|
|
1434
|
-
catch { /* best-effort — a missed stash just means no efficacy sample this turn */ }
|
|
1435
|
-
}
|
|
1436
|
-
/** Read + DELETE the pending injection stash (consume-once, so a turn with no injection never
|
|
1437
|
-
* reuses a stale sample). Returns null on the common no-stash path. */
|
|
1438
|
-
function consumeMemoryInjection(short) {
|
|
1439
|
-
try {
|
|
1440
|
-
const file = memoryReflexPendingPath(short);
|
|
1441
|
-
const raw = fs.readFileSync(file, 'utf-8');
|
|
1442
|
-
try {
|
|
1443
|
-
fs.unlinkSync(file);
|
|
1444
|
-
}
|
|
1445
|
-
catch { /* already gone — fine */ }
|
|
1446
|
-
const p = JSON.parse(raw);
|
|
1447
|
-
if (!p || typeof p.projectId !== 'string' || typeof p.hitText !== 'string')
|
|
1448
|
-
return null;
|
|
1449
|
-
return { projectId: p.projectId, hitText: p.hitText };
|
|
1450
|
-
}
|
|
1451
|
-
catch {
|
|
1452
|
-
return null; // no stash this turn (the common case)
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
/** Bump the memory-reflex EFFICACY numerator (the response drew on the injection) — best-effort. */
|
|
1456
|
-
function recordMemoryUsed(projectId) {
|
|
1457
|
-
try {
|
|
1458
|
-
const file = memoryReflexStatsPath(projectId);
|
|
1459
|
-
let prior = null;
|
|
1460
|
-
try {
|
|
1461
|
-
prior = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
1462
|
-
}
|
|
1463
|
-
catch { /* none yet */ }
|
|
1464
|
-
const next = (0, memory_reflex_1.tallyMemoryUsed)(prior);
|
|
1465
|
-
const dir = path.dirname(file);
|
|
1466
|
-
if (!fs.existsSync(dir))
|
|
1467
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1468
|
-
fs.writeFileSync(file, JSON.stringify(next, null, 2) + '\n');
|
|
1469
|
-
}
|
|
1470
|
-
catch { /* counter is best-effort */ }
|
|
1471
|
-
}
|
|
1472
|
-
/** Local cache of the SERVER's auto-quiet verdict for this project (the
|
|
1473
|
-
* mechanicKilled() analog for the memory-reflex). The Flash-Lite efficacy judge
|
|
1474
|
-
* decides the threshold server-side and ships the boolean on the /v1/memory/query
|
|
1475
|
-
* response; the hook caches it here and `memoryReflexQuieted` honors it with a
|
|
1476
|
-
* TTL (quietCacheSaysSilent). Beside the fire-counter under ~/.greprag/state. */
|
|
1477
|
-
function memoryReflexQuietPath(projectId) {
|
|
1478
|
-
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
1479
|
-
return path.join(home, '.greprag', 'state', `memory-reflex-quiet-${projectId}.json`);
|
|
1480
|
-
}
|
|
1481
|
-
/** Has the server's efficacy judge QUIETED this project's memory-reflex (low
|
|
1482
|
-
* used÷injected over a meaningful sample)? Reads the local cache; honors it only
|
|
1483
|
-
* while fresh (TTL) so a recovered reflex re-probes instead of staying silenced.
|
|
1484
|
-
* Fail-quiet: any error reads as NOT quieted (the reflex keeps working) —
|
|
1485
|
-
* mirrors mechanicKilled()'s fail-open posture. */
|
|
1486
|
-
function memoryReflexQuieted(projectId) {
|
|
1487
|
-
try {
|
|
1488
|
-
const raw = fs.readFileSync(memoryReflexQuietPath(projectId), 'utf-8');
|
|
1489
|
-
return (0, memory_reflex_1.quietCacheSaysSilent)(JSON.parse(raw), Date.now());
|
|
1490
|
-
}
|
|
1491
|
-
catch {
|
|
1492
|
-
return false;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
/** Cache the server's auto-quiet verdict from a /v1/memory/query response — written
|
|
1496
|
-
* whenever the reflex actually probes (fired the search). Best-effort. */
|
|
1497
|
-
function cacheMemoryReflexQuiet(projectId, quiet) {
|
|
1498
|
-
try {
|
|
1499
|
-
const file = memoryReflexQuietPath(projectId);
|
|
1500
|
-
const dir = path.dirname(file);
|
|
1501
|
-
if (!fs.existsSync(dir))
|
|
1502
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1503
|
-
const payload = { quiet, cachedAt: new Date().toISOString() };
|
|
1504
|
-
fs.writeFileSync(file, JSON.stringify(payload) + '\n');
|
|
1505
|
-
}
|
|
1506
|
-
catch { /* best-effort — a missed cache just means the reflex re-probes next turn */ }
|
|
1507
|
-
}
|
|
1508
1406
|
/** The reminder-interrupt registry CONTAINER call (docs/reminder-interrupt.md §Registry
|
|
1509
1407
|
* spec) — invoked from the proven-registered `notify` hook (claude-code path). Assembles
|
|
1510
1408
|
* the live env (the ONLY i/o: armed via isLocallyArmed, this session's own unread, the
|
|
1511
1409
|
* Stop-stashed stress/turnCount, the front-desk envelope notice) and fires every module
|
|
1512
1410
|
* whose detector reports deficient: watcher-arm (unarmed → nag if a peer messaged you, else
|
|
1513
|
-
* nudge; silent when armed), the stress-gated mechanic-friction, front-desk (a soft
|
|
1514
|
-
* "you've got mail" when a new envelope landed)
|
|
1515
|
-
*
|
|
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).
|
|
1516
1414
|
* The announce-only modules (setup-warning / checkpoint / assistant-doctrine) stay silent per
|
|
1517
1415
|
* turn — their env signals are SessionStart-only. Stacking; emitted once. Fail-open — any miss
|
|
1518
1416
|
* → silent. The Mechanic kill switch drops ONLY the mechanic module; the rest keep working. */
|
|
@@ -1539,41 +1437,22 @@ async function injectReminders(input, cfg, short) {
|
|
|
1539
1437
|
// null when nothing new. This is the network call the retired `mail` hook used to make;
|
|
1540
1438
|
// it moves here so the whole announce/reminder surface rides ONE detector loop.
|
|
1541
1439
|
const frontDeskNotice = await (0, front_desk_mail_1.buildFrontDeskNotice)(cfg.apiUrl, cfg.apiKey);
|
|
1542
|
-
//
|
|
1543
|
-
//
|
|
1544
|
-
//
|
|
1545
|
-
//
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
1548
|
-
let memoryHit = null;
|
|
1549
|
-
const prompt = typeof input.prompt === 'string' ? input.prompt : '';
|
|
1550
|
-
// Pick the memory query: recall-intent in THIS prompt fires an inline search. One
|
|
1551
|
-
// injection per turn. (The self-report [[recall:]] marker was removed 2026-06-22.)
|
|
1552
|
-
let memQuery = null;
|
|
1553
|
-
const memTrigger = 'keyword';
|
|
1554
|
-
if ((0, memory_reflex_1.hasRecallIntent)(prompt)) {
|
|
1555
|
-
memQuery = prompt.slice(0, 500);
|
|
1556
|
-
}
|
|
1557
|
-
// Auto-quiet gate (adr/memory-reflex.md): if the server's Flash-Lite efficacy judge has
|
|
1558
|
-
// QUIETED this project's reflex (low used÷injected over a meaningful sample), skip the search
|
|
1559
|
-
// entirely — the cached verdict expires on a TTL so a recovered reflex re-probes. mechanicKilled() analog.
|
|
1560
|
-
if (anchor.projectId && memQuery && !memoryReflexQuieted(anchor.projectId)) {
|
|
1561
|
-
const { hits, quiet } = await fetchMemoryHits(cfg.apiUrl, cfg.apiKey, anchor.projectId, memQuery);
|
|
1562
|
-
cacheMemoryReflexQuiet(anchor.projectId, quiet); // refresh the verdict from this probe
|
|
1563
|
-
memoryHit = (0, memory_reflex_1.buildMemoryInjection)(hits);
|
|
1564
|
-
recordMemoryFire(anchor.projectId, memTrigger, !!memoryHit, turnCount);
|
|
1565
|
-
// Stash what was injected so the Stop hook can score whether the response used it
|
|
1566
|
-
// (the efficacy numerator). Only when something was actually surfaced.
|
|
1567
|
-
if (memoryHit)
|
|
1568
|
-
stashMemoryInjection(short, anchor.projectId, (0, memory_reflex_1.gatedHitText)(hits), turnCount);
|
|
1569
|
-
}
|
|
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).
|
|
1570
1446
|
const env = {
|
|
1571
1447
|
short, turnCount, stress, armed, sessionUnread,
|
|
1572
1448
|
ownerPid, alias: (0, session_id_1.readIdentityAlias)(), assistant,
|
|
1573
|
-
frontDeskNotice,
|
|
1449
|
+
frontDeskNotice,
|
|
1574
1450
|
};
|
|
1575
|
-
//
|
|
1576
|
-
|
|
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;
|
|
1577
1456
|
const fired = (0, reminder_registry_1.collectReminders)(env, reg);
|
|
1578
1457
|
if (!fired.length)
|
|
1579
1458
|
return;
|
|
@@ -1753,7 +1632,7 @@ function validateChip(title, prompt) {
|
|
|
1753
1632
|
* adr/spawn-task-hook-mode.md 2026-05-27 entry — Tier 2 augmentation
|
|
1754
1633
|
* attempt). `permissionDecision: deny` IS honored, so the validator
|
|
1755
1634
|
* catches missing Block 1 / Block 2 markers; the agent re-composes the
|
|
1756
|
-
* call with the templates from
|
|
1635
|
+
* call with the templates from `greprag load chip-spawn`. */
|
|
1757
1636
|
function handlePreSpawnCheck(input) {
|
|
1758
1637
|
if (input.tool_name !== 'mcp__ccd_session__spawn_task')
|
|
1759
1638
|
return;
|
|
@@ -1764,7 +1643,7 @@ function handlePreSpawnCheck(input) {
|
|
|
1764
1643
|
return;
|
|
1765
1644
|
const reason = `Chip prompt rejected by greprag PreToolUse validator. Fix the following and re-call spawn_task:\n - ` +
|
|
1766
1645
|
violations.join('\n - ') +
|
|
1767
|
-
`\n\nFull conventions:
|
|
1646
|
+
`\n\nFull conventions: run \`greprag load chip-spawn\`.`;
|
|
1768
1647
|
process.stdout.write(JSON.stringify({
|
|
1769
1648
|
hookSpecificOutput: {
|
|
1770
1649
|
hookEventName: 'PreToolUse',
|
|
@@ -1835,7 +1714,8 @@ async function coordinateGate(input) {
|
|
|
1835
1714
|
if (!short)
|
|
1836
1715
|
return; // no session id → silent
|
|
1837
1716
|
// Local + cheap: classify BEFORE any network so non-risky calls cost nothing.
|
|
1838
|
-
const
|
|
1717
|
+
const toolInput = (input.tool_input || {});
|
|
1718
|
+
const trigger = (0, coordinate_gate_1.triggerFromPreToolUse)(input.tool_name || '', toolInput);
|
|
1839
1719
|
if (!trigger)
|
|
1840
1720
|
return; // not a risky action → silent
|
|
1841
1721
|
let anchor;
|
|
@@ -1847,7 +1727,8 @@ async function coordinateGate(input) {
|
|
|
1847
1727
|
}
|
|
1848
1728
|
if (!anchor.projectId)
|
|
1849
1729
|
return; // unanchored → nothing to match
|
|
1850
|
-
|
|
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)({
|
|
1851
1732
|
short,
|
|
1852
1733
|
projectId: anchor.projectId,
|
|
1853
1734
|
projectName: anchor.projectName,
|
|
@@ -1855,15 +1736,26 @@ async function coordinateGate(input) {
|
|
|
1855
1736
|
apiKey: cfg.apiKey,
|
|
1856
1737
|
alias: (0, session_id_1.readIdentityAlias)(),
|
|
1857
1738
|
});
|
|
1858
|
-
if (
|
|
1739
|
+
if (peers.length === 0)
|
|
1859
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;
|
|
1860
1752
|
// EFFECT: inject as additionalContext — advisory heads-up, NO permission pause.
|
|
1861
1753
|
// `ask` was too intrusive (prompted on every git op while peers were live); inject
|
|
1862
1754
|
// keeps the agent aware without a manual-allow. adr: adr/monitor-resilience.md
|
|
1863
1755
|
process.stdout.write(JSON.stringify({
|
|
1864
1756
|
hookSpecificOutput: {
|
|
1865
1757
|
hookEventName: input.hook_event_name || 'PreToolUse',
|
|
1866
|
-
additionalContext:
|
|
1758
|
+
additionalContext: fired.map(f => f.line).join('\n\n'),
|
|
1867
1759
|
},
|
|
1868
1760
|
}) + '\n');
|
|
1869
1761
|
}
|