mirror of
https://github.com/lukaszraczylo/claude-adam.git
synced 2026-10-21 06:27:53 +00:00
feat(nudge): ReContext-grounded cue reactivation + analyst refinements
Analyse claude-adam through ReContext (arXiv:2607.02509): close the access!=utilization gap with query-conditioned nudge replay + analyst tweaks. - nudge: optional cue.cwd gates SessionStart replay (adam-nudge.mjs) and new mid-session reactivation on UserPromptSubmit (adam-observe.mjs, cwd-gated, once/session, ADAM_NO_MIDSESSION_NUDGE opt-out). - new adam-apply-nudge.mjs builds message + cue deterministically from proposal frontmatter (wired into SKILL.md, install.sh, pending-check). - adam-batch: optional --max-batches/--top-fraction output cap (default off), reports capped. - analyst: utility-lift rubric penalty + skill_utility_path fed to both stages; adaptive re-diagnose for unclear/top-severity clusters; cross-type associative pass (agents/adam.md, SKILL.md). - tests: 140 -> 144 (cue gate, batch cap, apply-nudge, mid-session).
This commit is contained in:
@@ -289,6 +289,37 @@ function errorFingerprint(toolResponse) {
|
||||
if (!normalized) return null;
|
||||
return `raw:${djb2(normalized)}`;
|
||||
}
|
||||
// Mid-session nudge reactivation (ReContext P5/P7): on UserPromptSubmit, surface a
|
||||
// stored active-nudge whose `cue.cwd` matches the current directory, so a deferred
|
||||
// reminder lands when the user is actually working in that project instead of only
|
||||
// at SessionStart. Read-only — SessionStart (adam-nudge.mjs) owns display accounting;
|
||||
// this path surfaces each matching nudge at most once per session (state.surfaced_nudges)
|
||||
// and respects expiry + max_displays. Opt out with ADAM_NO_MIDSESSION_NUDGE.
|
||||
// UserPromptSubmit hook stdout is injected into the session context, so this is the
|
||||
// reliable surface (unlike PostToolUse). Any failure is swallowed — never block a prompt.
|
||||
function surfaceMatchingNudges(state, cwd) {
|
||||
if (process.env.ADAM_NO_MIDSESSION_NUDGE || !cwd) return;
|
||||
const nudgesPath = join(ROOT, "active-nudges.json");
|
||||
let raw;
|
||||
try { raw = JSON.parse(readFileSync(nudgesPath, "utf8")); } catch { return; }
|
||||
if (!Array.isArray(raw)) return;
|
||||
const now = Date.now();
|
||||
if (!Array.isArray(state.surfaced_nudges)) state.surfaced_nudges = [];
|
||||
for (const n of raw) {
|
||||
if (!n || typeof n !== "object" || typeof n.message !== "string" || !n.message) continue;
|
||||
if (Number(n.expires_at_ts || 0) <= now) continue;
|
||||
const max = Number(n.max_displays || 0);
|
||||
if (max > 0 && Number(n.displays_used || 0) >= max) continue;
|
||||
const cue = n.cue && typeof n.cue === "object" ? n.cue : null;
|
||||
if (!cue || !cue.cwd || cue.cwd !== cwd) continue; // cue-conditioned on cwd
|
||||
const id = String(n.created_at || n.message);
|
||||
if (state.surfaced_nudges.includes(id)) continue;
|
||||
process.stdout.write(n.message + "\n");
|
||||
state.surfaced_nudges.push(id);
|
||||
if (state.surfaced_nudges.length > 50) state.surfaced_nudges.shift();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function resetFrictionCounters(state) {
|
||||
state.tools_since_user = 0;
|
||||
@@ -315,6 +346,7 @@ function resetSessionLocal(state) {
|
||||
state.task_tool_kinds = {};
|
||||
state.task_tool_count = 0;
|
||||
state.task_corrections = 0;
|
||||
state.surfaced_nudges = [];
|
||||
}
|
||||
|
||||
function ensureStateDefaults(state) {
|
||||
@@ -339,6 +371,7 @@ function ensureStateDefaults(state) {
|
||||
if (!Array.isArray(state.recentRecoveries)) state.recentRecoveries = [];
|
||||
if (typeof state.session_post_count !== "number") state.session_post_count = 0;
|
||||
if (!Array.isArray(state.context_ring)) state.context_ring = [];
|
||||
if (!Array.isArray(state.surfaced_nudges)) state.surfaced_nudges = [];
|
||||
}
|
||||
|
||||
function main() {
|
||||
@@ -364,6 +397,7 @@ function main() {
|
||||
if (event === "UserPromptSubmit") {
|
||||
const prompt = (input.prompt || "").slice(0, 200);
|
||||
pushContext(state, { event: "user", prompt: excerpt(prompt, CONTEXT_EXCERPT_LEN), ts });
|
||||
surfaceMatchingNudges(state, cwd);
|
||||
if (isCorrection(prompt)) {
|
||||
const last = state.tool_window[state.tool_window.length - 1] || {};
|
||||
appendJournal({
|
||||
|
||||
Reference in New Issue
Block a user